@camstack/addon-provider-rtsp 0.1.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/dist/index.d.mts +70 -0
- package/dist/index.d.ts +70 -0
- package/dist/index.js +343 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +314 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +44 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { ICamstackAddon, IConfigurable, AddonManifest, AddonContext, CapabilityProviderMap, ConfigUISchema, IDevice, DeviceType, DeviceCapabilityName, CamstackContext, IDeviceCapability, DeviceState, DeviceMetadata, IDeviceProvider, ProviderStatus, DiscoveredDevice, LiveEvent } from '@camstack/types';
|
|
2
|
+
|
|
3
|
+
declare class RtspProviderAddon implements ICamstackAddon, IConfigurable {
|
|
4
|
+
readonly manifest: AddonManifest;
|
|
5
|
+
private provider;
|
|
6
|
+
initialize(context: AddonContext): Promise<void>;
|
|
7
|
+
shutdown(): Promise<void>;
|
|
8
|
+
getCapabilityProvider<K extends keyof CapabilityProviderMap>(name: K): CapabilityProviderMap[K] | null;
|
|
9
|
+
getConfigSchema(): ConfigUISchema;
|
|
10
|
+
getConfig(): Record<string, unknown>;
|
|
11
|
+
onConfigChange(_config: Record<string, unknown>): Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface RtspCameraConfig {
|
|
15
|
+
readonly id: string;
|
|
16
|
+
readonly name: string;
|
|
17
|
+
readonly url: string;
|
|
18
|
+
readonly subStreamUrl?: string;
|
|
19
|
+
readonly snapshotUrl?: string;
|
|
20
|
+
readonly audioEnabled?: boolean;
|
|
21
|
+
readonly width?: number;
|
|
22
|
+
readonly height?: number;
|
|
23
|
+
}
|
|
24
|
+
interface RtspProviderConfig {
|
|
25
|
+
readonly id: string;
|
|
26
|
+
readonly name: string;
|
|
27
|
+
readonly cameras: readonly RtspCameraConfig[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
declare class RtspDevice implements IDevice {
|
|
31
|
+
private readonly config;
|
|
32
|
+
readonly id: string;
|
|
33
|
+
readonly name: string;
|
|
34
|
+
readonly providerId: string;
|
|
35
|
+
readonly type: DeviceType;
|
|
36
|
+
readonly capabilities: DeviceCapabilityName[];
|
|
37
|
+
readonly ctx: CamstackContext;
|
|
38
|
+
private readonly capabilityMap;
|
|
39
|
+
constructor(config: RtspCameraConfig, providerId: string, ctx: CamstackContext);
|
|
40
|
+
getCapability<T extends IDeviceCapability>(cap: DeviceCapabilityName): T | null;
|
|
41
|
+
hasCapability(cap: DeviceCapabilityName): boolean;
|
|
42
|
+
getState(): DeviceState;
|
|
43
|
+
getMetadata(): DeviceMetadata;
|
|
44
|
+
private createCamera;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
declare class RtspProvider implements IDeviceProvider {
|
|
48
|
+
readonly id: string;
|
|
49
|
+
readonly type = "rtsp";
|
|
50
|
+
readonly name: string;
|
|
51
|
+
readonly discoveryMode: 'manual';
|
|
52
|
+
readonly ctx: CamstackContext;
|
|
53
|
+
private readonly devices;
|
|
54
|
+
constructor(config: RtspProviderConfig, ctx: CamstackContext);
|
|
55
|
+
start(): Promise<void>;
|
|
56
|
+
stop(): Promise<void>;
|
|
57
|
+
getStatus(): ProviderStatus;
|
|
58
|
+
discoverDevices(): Promise<DiscoveredDevice[]>;
|
|
59
|
+
getDevices(): IDevice[];
|
|
60
|
+
getDeviceConfigSchema(): {
|
|
61
|
+
id: string;
|
|
62
|
+
name: string;
|
|
63
|
+
url: string;
|
|
64
|
+
subStreamUrl: string;
|
|
65
|
+
snapshotUrl: string;
|
|
66
|
+
};
|
|
67
|
+
subscribeLiveEvents(_callback: (event: LiveEvent) => void): () => void;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export { type RtspCameraConfig, RtspDevice, RtspProvider, RtspProviderAddon, type RtspProviderConfig };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { ICamstackAddon, IConfigurable, AddonManifest, AddonContext, CapabilityProviderMap, ConfigUISchema, IDevice, DeviceType, DeviceCapabilityName, CamstackContext, IDeviceCapability, DeviceState, DeviceMetadata, IDeviceProvider, ProviderStatus, DiscoveredDevice, LiveEvent } from '@camstack/types';
|
|
2
|
+
|
|
3
|
+
declare class RtspProviderAddon implements ICamstackAddon, IConfigurable {
|
|
4
|
+
readonly manifest: AddonManifest;
|
|
5
|
+
private provider;
|
|
6
|
+
initialize(context: AddonContext): Promise<void>;
|
|
7
|
+
shutdown(): Promise<void>;
|
|
8
|
+
getCapabilityProvider<K extends keyof CapabilityProviderMap>(name: K): CapabilityProviderMap[K] | null;
|
|
9
|
+
getConfigSchema(): ConfigUISchema;
|
|
10
|
+
getConfig(): Record<string, unknown>;
|
|
11
|
+
onConfigChange(_config: Record<string, unknown>): Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface RtspCameraConfig {
|
|
15
|
+
readonly id: string;
|
|
16
|
+
readonly name: string;
|
|
17
|
+
readonly url: string;
|
|
18
|
+
readonly subStreamUrl?: string;
|
|
19
|
+
readonly snapshotUrl?: string;
|
|
20
|
+
readonly audioEnabled?: boolean;
|
|
21
|
+
readonly width?: number;
|
|
22
|
+
readonly height?: number;
|
|
23
|
+
}
|
|
24
|
+
interface RtspProviderConfig {
|
|
25
|
+
readonly id: string;
|
|
26
|
+
readonly name: string;
|
|
27
|
+
readonly cameras: readonly RtspCameraConfig[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
declare class RtspDevice implements IDevice {
|
|
31
|
+
private readonly config;
|
|
32
|
+
readonly id: string;
|
|
33
|
+
readonly name: string;
|
|
34
|
+
readonly providerId: string;
|
|
35
|
+
readonly type: DeviceType;
|
|
36
|
+
readonly capabilities: DeviceCapabilityName[];
|
|
37
|
+
readonly ctx: CamstackContext;
|
|
38
|
+
private readonly capabilityMap;
|
|
39
|
+
constructor(config: RtspCameraConfig, providerId: string, ctx: CamstackContext);
|
|
40
|
+
getCapability<T extends IDeviceCapability>(cap: DeviceCapabilityName): T | null;
|
|
41
|
+
hasCapability(cap: DeviceCapabilityName): boolean;
|
|
42
|
+
getState(): DeviceState;
|
|
43
|
+
getMetadata(): DeviceMetadata;
|
|
44
|
+
private createCamera;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
declare class RtspProvider implements IDeviceProvider {
|
|
48
|
+
readonly id: string;
|
|
49
|
+
readonly type = "rtsp";
|
|
50
|
+
readonly name: string;
|
|
51
|
+
readonly discoveryMode: 'manual';
|
|
52
|
+
readonly ctx: CamstackContext;
|
|
53
|
+
private readonly devices;
|
|
54
|
+
constructor(config: RtspProviderConfig, ctx: CamstackContext);
|
|
55
|
+
start(): Promise<void>;
|
|
56
|
+
stop(): Promise<void>;
|
|
57
|
+
getStatus(): ProviderStatus;
|
|
58
|
+
discoverDevices(): Promise<DiscoveredDevice[]>;
|
|
59
|
+
getDevices(): IDevice[];
|
|
60
|
+
getDeviceConfigSchema(): {
|
|
61
|
+
id: string;
|
|
62
|
+
name: string;
|
|
63
|
+
url: string;
|
|
64
|
+
subStreamUrl: string;
|
|
65
|
+
snapshotUrl: string;
|
|
66
|
+
};
|
|
67
|
+
subscribeLiveEvents(_callback: (event: LiveEvent) => void): () => void;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export { type RtspCameraConfig, RtspDevice, RtspProvider, RtspProviderAddon, type RtspProviderConfig };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
RtspDevice: () => RtspDevice,
|
|
24
|
+
RtspProvider: () => RtspProvider,
|
|
25
|
+
RtspProviderAddon: () => RtspProviderAddon
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(index_exports);
|
|
28
|
+
|
|
29
|
+
// src/rtsp-device.ts
|
|
30
|
+
var RtspDevice = class {
|
|
31
|
+
constructor(config, providerId, ctx) {
|
|
32
|
+
this.config = config;
|
|
33
|
+
this.id = `${providerId}/${config.id}`;
|
|
34
|
+
this.name = config.name;
|
|
35
|
+
this.providerId = providerId;
|
|
36
|
+
this.ctx = ctx;
|
|
37
|
+
this.capabilities = ["camera"];
|
|
38
|
+
this.capabilityMap.set("camera", this.createCamera());
|
|
39
|
+
}
|
|
40
|
+
id;
|
|
41
|
+
name;
|
|
42
|
+
providerId;
|
|
43
|
+
type = "camera";
|
|
44
|
+
capabilities;
|
|
45
|
+
ctx;
|
|
46
|
+
capabilityMap = /* @__PURE__ */ new Map();
|
|
47
|
+
getCapability(cap) {
|
|
48
|
+
return this.capabilityMap.get(cap) ?? null;
|
|
49
|
+
}
|
|
50
|
+
hasCapability(cap) {
|
|
51
|
+
return this.capabilityMap.has(cap);
|
|
52
|
+
}
|
|
53
|
+
getState() {
|
|
54
|
+
return { online: true };
|
|
55
|
+
}
|
|
56
|
+
getMetadata() {
|
|
57
|
+
return { manufacturer: "Generic RTSP" };
|
|
58
|
+
}
|
|
59
|
+
createCamera() {
|
|
60
|
+
const cfg = this.config;
|
|
61
|
+
return {
|
|
62
|
+
kind: "camera",
|
|
63
|
+
async getSnapshot() {
|
|
64
|
+
if (cfg.snapshotUrl) {
|
|
65
|
+
const res = await fetch(cfg.snapshotUrl);
|
|
66
|
+
return Buffer.from(await res.arrayBuffer());
|
|
67
|
+
}
|
|
68
|
+
return Buffer.alloc(0);
|
|
69
|
+
},
|
|
70
|
+
async getStreamOptions() {
|
|
71
|
+
const options = [
|
|
72
|
+
{
|
|
73
|
+
id: `${cfg.id}_main`,
|
|
74
|
+
label: "Main",
|
|
75
|
+
protocol: "rtsp",
|
|
76
|
+
quality: "main",
|
|
77
|
+
url: cfg.url
|
|
78
|
+
}
|
|
79
|
+
];
|
|
80
|
+
if (cfg.subStreamUrl) {
|
|
81
|
+
options.push({
|
|
82
|
+
id: `${cfg.id}_sub`,
|
|
83
|
+
label: "Sub",
|
|
84
|
+
protocol: "rtsp",
|
|
85
|
+
quality: "sub",
|
|
86
|
+
url: cfg.subStreamUrl
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
return options;
|
|
90
|
+
},
|
|
91
|
+
async getStreamUrl(option) {
|
|
92
|
+
return option.url ?? cfg.url;
|
|
93
|
+
},
|
|
94
|
+
getConnectionMode() {
|
|
95
|
+
return "always-on";
|
|
96
|
+
},
|
|
97
|
+
async setConnectionMode() {
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// src/element-config-store.ts
|
|
104
|
+
var ElementConfigStore = class {
|
|
105
|
+
constructor(elementId, storage) {
|
|
106
|
+
this.elementId = elementId;
|
|
107
|
+
this.storage = storage;
|
|
108
|
+
}
|
|
109
|
+
cache = {};
|
|
110
|
+
listeners = /* @__PURE__ */ new Set();
|
|
111
|
+
loaded = false;
|
|
112
|
+
/** Load config from storage into cache. Called once on first access. */
|
|
113
|
+
async ensureLoaded() {
|
|
114
|
+
if (this.loaded) return;
|
|
115
|
+
if (!this.storage.structured) {
|
|
116
|
+
this.loaded = true;
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
const records = await this.storage.structured.query("config", {
|
|
121
|
+
where: { id: this.elementId },
|
|
122
|
+
limit: 1
|
|
123
|
+
});
|
|
124
|
+
if (records.length > 0) {
|
|
125
|
+
this.cache = records[0].data ?? {};
|
|
126
|
+
}
|
|
127
|
+
} catch {
|
|
128
|
+
}
|
|
129
|
+
this.loaded = true;
|
|
130
|
+
}
|
|
131
|
+
getAll() {
|
|
132
|
+
return { ...this.cache };
|
|
133
|
+
}
|
|
134
|
+
get(key) {
|
|
135
|
+
const parts = key.split(".");
|
|
136
|
+
let current = this.cache;
|
|
137
|
+
for (const part of parts) {
|
|
138
|
+
if (current == null || typeof current !== "object") return void 0;
|
|
139
|
+
current = current[part];
|
|
140
|
+
}
|
|
141
|
+
return current;
|
|
142
|
+
}
|
|
143
|
+
async set(key, value) {
|
|
144
|
+
await this.ensureLoaded();
|
|
145
|
+
setNestedValue(this.cache, key, value);
|
|
146
|
+
await this.persist();
|
|
147
|
+
this.notifyListeners();
|
|
148
|
+
}
|
|
149
|
+
async setAll(config) {
|
|
150
|
+
await this.ensureLoaded();
|
|
151
|
+
this.cache = { ...config };
|
|
152
|
+
await this.persist();
|
|
153
|
+
this.notifyListeners();
|
|
154
|
+
}
|
|
155
|
+
onChange(callback) {
|
|
156
|
+
this.listeners.add(callback);
|
|
157
|
+
return () => {
|
|
158
|
+
this.listeners.delete(callback);
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/** Initialize from storage — called by ContextFactory after creation */
|
|
162
|
+
async load() {
|
|
163
|
+
await this.ensureLoaded();
|
|
164
|
+
}
|
|
165
|
+
/** Initialize with default values (doesn't overwrite existing) */
|
|
166
|
+
async loadDefaults(defaults) {
|
|
167
|
+
await this.ensureLoaded();
|
|
168
|
+
if (Object.keys(this.cache).length === 0) {
|
|
169
|
+
this.cache = { ...defaults };
|
|
170
|
+
await this.persist();
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
async persist() {
|
|
174
|
+
if (!this.storage.structured) return;
|
|
175
|
+
try {
|
|
176
|
+
const existing = await this.storage.structured.query("config", {
|
|
177
|
+
where: { id: this.elementId },
|
|
178
|
+
limit: 1
|
|
179
|
+
});
|
|
180
|
+
if (existing.length > 0) {
|
|
181
|
+
await this.storage.structured.update("config", this.elementId, this.cache);
|
|
182
|
+
} else {
|
|
183
|
+
await this.storage.structured.insert({
|
|
184
|
+
collection: "config",
|
|
185
|
+
id: this.elementId,
|
|
186
|
+
data: this.cache
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
} catch {
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
notifyListeners() {
|
|
193
|
+
const snapshot = this.getAll();
|
|
194
|
+
for (const listener of this.listeners) {
|
|
195
|
+
try {
|
|
196
|
+
listener(snapshot);
|
|
197
|
+
} catch {
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
function setNestedValue(obj, path, value) {
|
|
203
|
+
const parts = path.split(".");
|
|
204
|
+
let current = obj;
|
|
205
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
206
|
+
const part = parts[i];
|
|
207
|
+
if (!(part in current) || typeof current[part] !== "object" || current[part] === null) {
|
|
208
|
+
current[part] = {};
|
|
209
|
+
}
|
|
210
|
+
current = current[part];
|
|
211
|
+
}
|
|
212
|
+
current[parts[parts.length - 1]] = value;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// src/rtsp-provider.ts
|
|
216
|
+
var RtspProvider = class {
|
|
217
|
+
id;
|
|
218
|
+
type = "rtsp";
|
|
219
|
+
name;
|
|
220
|
+
discoveryMode = "manual";
|
|
221
|
+
ctx;
|
|
222
|
+
devices;
|
|
223
|
+
constructor(config, ctx) {
|
|
224
|
+
this.id = config.id;
|
|
225
|
+
this.name = config.name;
|
|
226
|
+
this.ctx = ctx;
|
|
227
|
+
const built = [];
|
|
228
|
+
for (const cam of config.cameras) {
|
|
229
|
+
const deviceCtx = {
|
|
230
|
+
id: `device:${this.id}/${cam.id}`,
|
|
231
|
+
logger: ctx.logger.child(cam.name),
|
|
232
|
+
eventBus: ctx.eventBus,
|
|
233
|
+
storage: ctx.storage,
|
|
234
|
+
config: new ElementConfigStore(`device:${this.id}/${cam.id}`, ctx.storage)
|
|
235
|
+
};
|
|
236
|
+
built.push(new RtspDevice(cam, this.id, deviceCtx));
|
|
237
|
+
}
|
|
238
|
+
this.devices = built;
|
|
239
|
+
}
|
|
240
|
+
async start() {
|
|
241
|
+
this.ctx.logger.info(`RTSP provider started with ${this.devices.length} cameras`);
|
|
242
|
+
}
|
|
243
|
+
async stop() {
|
|
244
|
+
this.ctx.logger.info("RTSP provider stopped");
|
|
245
|
+
}
|
|
246
|
+
getStatus() {
|
|
247
|
+
return { connected: true, deviceCount: this.devices.length };
|
|
248
|
+
}
|
|
249
|
+
async discoverDevices() {
|
|
250
|
+
return [];
|
|
251
|
+
}
|
|
252
|
+
getDevices() {
|
|
253
|
+
return [...this.devices];
|
|
254
|
+
}
|
|
255
|
+
getDeviceConfigSchema() {
|
|
256
|
+
return {
|
|
257
|
+
id: "string",
|
|
258
|
+
name: "string",
|
|
259
|
+
url: "string (rtsp://...)",
|
|
260
|
+
subStreamUrl: "string (optional)",
|
|
261
|
+
snapshotUrl: "string (optional)"
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
subscribeLiveEvents(_callback) {
|
|
265
|
+
return () => {
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
// src/addon.ts
|
|
271
|
+
var RtspProviderAddon = class {
|
|
272
|
+
manifest = {
|
|
273
|
+
id: "provider-rtsp",
|
|
274
|
+
name: "RTSP Camera Provider",
|
|
275
|
+
version: "0.1.0",
|
|
276
|
+
capabilities: ["device-provider"]
|
|
277
|
+
};
|
|
278
|
+
provider = null;
|
|
279
|
+
async initialize(context) {
|
|
280
|
+
const config = context.addonConfig;
|
|
281
|
+
if (!config.cameras || config.cameras.length === 0) {
|
|
282
|
+
context.logger.info("RTSP provider: no cameras configured");
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
const providerConfig = {
|
|
286
|
+
id: config.id ?? "rtsp-default",
|
|
287
|
+
name: config.name ?? "RTSP Cameras",
|
|
288
|
+
cameras: config.cameras
|
|
289
|
+
};
|
|
290
|
+
this.provider = new RtspProvider(providerConfig, {
|
|
291
|
+
id: context.id,
|
|
292
|
+
logger: context.logger,
|
|
293
|
+
eventBus: context.eventBus,
|
|
294
|
+
storage: context.storage,
|
|
295
|
+
config: context.config
|
|
296
|
+
});
|
|
297
|
+
context.logger.info("RTSP provider addon initialized");
|
|
298
|
+
}
|
|
299
|
+
async shutdown() {
|
|
300
|
+
await this.provider?.stop();
|
|
301
|
+
this.provider = null;
|
|
302
|
+
}
|
|
303
|
+
getCapabilityProvider(name) {
|
|
304
|
+
if (name === "device-provider" && this.provider) {
|
|
305
|
+
return this.provider;
|
|
306
|
+
}
|
|
307
|
+
return null;
|
|
308
|
+
}
|
|
309
|
+
getConfigSchema() {
|
|
310
|
+
return {
|
|
311
|
+
sections: [
|
|
312
|
+
{
|
|
313
|
+
id: "general",
|
|
314
|
+
title: "RTSP Provider",
|
|
315
|
+
description: "Configure generic RTSP camera connections",
|
|
316
|
+
columns: 1,
|
|
317
|
+
fields: [
|
|
318
|
+
{ type: "text", key: "name", label: "Provider Name", placeholder: "RTSP Cameras" },
|
|
319
|
+
{
|
|
320
|
+
type: "info",
|
|
321
|
+
key: "info",
|
|
322
|
+
label: "Camera Configuration",
|
|
323
|
+
content: "Individual cameras are configured via the device management interface after adding this provider.",
|
|
324
|
+
variant: "info"
|
|
325
|
+
}
|
|
326
|
+
]
|
|
327
|
+
}
|
|
328
|
+
]
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
getConfig() {
|
|
332
|
+
return {};
|
|
333
|
+
}
|
|
334
|
+
async onConfigChange(_config) {
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
338
|
+
0 && (module.exports = {
|
|
339
|
+
RtspDevice,
|
|
340
|
+
RtspProvider,
|
|
341
|
+
RtspProviderAddon
|
|
342
|
+
});
|
|
343
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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 type {\n IDevice,\n DeviceType,\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 = '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 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} from '@camstack/types'\nimport type { 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: readonly 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 config (immutable after construction)\n const built: RtspDevice[] = []\n for (const cam of config.cameras) {\n const deviceCtx: CamstackContext = {\n id: `device:${this.id}/${cam.id}`,\n logger: ctx.logger.child(cam.name),\n eventBus: ctx.eventBus,\n storage: ctx.storage,\n config: new ElementConfigStore(`device:${this.id}/${cam.id}`, ctx.storage),\n }\n built.push(new RtspDevice(cam, this.id, deviceCtx))\n }\n this.devices = built\n }\n\n async start(): Promise<void> {\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() {\n return {\n id: 'string',\n name: 'string',\n url: 'string (rtsp://...)',\n subStreamUrl: 'string (optional)',\n snapshotUrl: 'string (optional)',\n }\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","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 capabilities: ['device-provider'],\n }\n\n private provider: RtspProvider | null = null\n\n async initialize(context: AddonContext): Promise<void> {\n const config = context.addonConfig as unknown as RtspProviderConfig\n\n if (!config.cameras || config.cameras.length === 0) {\n context.logger.info('RTSP provider: no cameras configured')\n return\n }\n\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 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":[]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
// src/rtsp-device.ts
|
|
2
|
+
var RtspDevice = class {
|
|
3
|
+
constructor(config, providerId, ctx) {
|
|
4
|
+
this.config = config;
|
|
5
|
+
this.id = `${providerId}/${config.id}`;
|
|
6
|
+
this.name = config.name;
|
|
7
|
+
this.providerId = providerId;
|
|
8
|
+
this.ctx = ctx;
|
|
9
|
+
this.capabilities = ["camera"];
|
|
10
|
+
this.capabilityMap.set("camera", this.createCamera());
|
|
11
|
+
}
|
|
12
|
+
id;
|
|
13
|
+
name;
|
|
14
|
+
providerId;
|
|
15
|
+
type = "camera";
|
|
16
|
+
capabilities;
|
|
17
|
+
ctx;
|
|
18
|
+
capabilityMap = /* @__PURE__ */ new Map();
|
|
19
|
+
getCapability(cap) {
|
|
20
|
+
return this.capabilityMap.get(cap) ?? null;
|
|
21
|
+
}
|
|
22
|
+
hasCapability(cap) {
|
|
23
|
+
return this.capabilityMap.has(cap);
|
|
24
|
+
}
|
|
25
|
+
getState() {
|
|
26
|
+
return { online: true };
|
|
27
|
+
}
|
|
28
|
+
getMetadata() {
|
|
29
|
+
return { manufacturer: "Generic RTSP" };
|
|
30
|
+
}
|
|
31
|
+
createCamera() {
|
|
32
|
+
const cfg = this.config;
|
|
33
|
+
return {
|
|
34
|
+
kind: "camera",
|
|
35
|
+
async getSnapshot() {
|
|
36
|
+
if (cfg.snapshotUrl) {
|
|
37
|
+
const res = await fetch(cfg.snapshotUrl);
|
|
38
|
+
return Buffer.from(await res.arrayBuffer());
|
|
39
|
+
}
|
|
40
|
+
return Buffer.alloc(0);
|
|
41
|
+
},
|
|
42
|
+
async getStreamOptions() {
|
|
43
|
+
const options = [
|
|
44
|
+
{
|
|
45
|
+
id: `${cfg.id}_main`,
|
|
46
|
+
label: "Main",
|
|
47
|
+
protocol: "rtsp",
|
|
48
|
+
quality: "main",
|
|
49
|
+
url: cfg.url
|
|
50
|
+
}
|
|
51
|
+
];
|
|
52
|
+
if (cfg.subStreamUrl) {
|
|
53
|
+
options.push({
|
|
54
|
+
id: `${cfg.id}_sub`,
|
|
55
|
+
label: "Sub",
|
|
56
|
+
protocol: "rtsp",
|
|
57
|
+
quality: "sub",
|
|
58
|
+
url: cfg.subStreamUrl
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
return options;
|
|
62
|
+
},
|
|
63
|
+
async getStreamUrl(option) {
|
|
64
|
+
return option.url ?? cfg.url;
|
|
65
|
+
},
|
|
66
|
+
getConnectionMode() {
|
|
67
|
+
return "always-on";
|
|
68
|
+
},
|
|
69
|
+
async setConnectionMode() {
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// src/element-config-store.ts
|
|
76
|
+
var ElementConfigStore = class {
|
|
77
|
+
constructor(elementId, storage) {
|
|
78
|
+
this.elementId = elementId;
|
|
79
|
+
this.storage = storage;
|
|
80
|
+
}
|
|
81
|
+
cache = {};
|
|
82
|
+
listeners = /* @__PURE__ */ new Set();
|
|
83
|
+
loaded = false;
|
|
84
|
+
/** Load config from storage into cache. Called once on first access. */
|
|
85
|
+
async ensureLoaded() {
|
|
86
|
+
if (this.loaded) return;
|
|
87
|
+
if (!this.storage.structured) {
|
|
88
|
+
this.loaded = true;
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
const records = await this.storage.structured.query("config", {
|
|
93
|
+
where: { id: this.elementId },
|
|
94
|
+
limit: 1
|
|
95
|
+
});
|
|
96
|
+
if (records.length > 0) {
|
|
97
|
+
this.cache = records[0].data ?? {};
|
|
98
|
+
}
|
|
99
|
+
} catch {
|
|
100
|
+
}
|
|
101
|
+
this.loaded = true;
|
|
102
|
+
}
|
|
103
|
+
getAll() {
|
|
104
|
+
return { ...this.cache };
|
|
105
|
+
}
|
|
106
|
+
get(key) {
|
|
107
|
+
const parts = key.split(".");
|
|
108
|
+
let current = this.cache;
|
|
109
|
+
for (const part of parts) {
|
|
110
|
+
if (current == null || typeof current !== "object") return void 0;
|
|
111
|
+
current = current[part];
|
|
112
|
+
}
|
|
113
|
+
return current;
|
|
114
|
+
}
|
|
115
|
+
async set(key, value) {
|
|
116
|
+
await this.ensureLoaded();
|
|
117
|
+
setNestedValue(this.cache, key, value);
|
|
118
|
+
await this.persist();
|
|
119
|
+
this.notifyListeners();
|
|
120
|
+
}
|
|
121
|
+
async setAll(config) {
|
|
122
|
+
await this.ensureLoaded();
|
|
123
|
+
this.cache = { ...config };
|
|
124
|
+
await this.persist();
|
|
125
|
+
this.notifyListeners();
|
|
126
|
+
}
|
|
127
|
+
onChange(callback) {
|
|
128
|
+
this.listeners.add(callback);
|
|
129
|
+
return () => {
|
|
130
|
+
this.listeners.delete(callback);
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/** Initialize from storage — called by ContextFactory after creation */
|
|
134
|
+
async load() {
|
|
135
|
+
await this.ensureLoaded();
|
|
136
|
+
}
|
|
137
|
+
/** Initialize with default values (doesn't overwrite existing) */
|
|
138
|
+
async loadDefaults(defaults) {
|
|
139
|
+
await this.ensureLoaded();
|
|
140
|
+
if (Object.keys(this.cache).length === 0) {
|
|
141
|
+
this.cache = { ...defaults };
|
|
142
|
+
await this.persist();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
async persist() {
|
|
146
|
+
if (!this.storage.structured) return;
|
|
147
|
+
try {
|
|
148
|
+
const existing = await this.storage.structured.query("config", {
|
|
149
|
+
where: { id: this.elementId },
|
|
150
|
+
limit: 1
|
|
151
|
+
});
|
|
152
|
+
if (existing.length > 0) {
|
|
153
|
+
await this.storage.structured.update("config", this.elementId, this.cache);
|
|
154
|
+
} else {
|
|
155
|
+
await this.storage.structured.insert({
|
|
156
|
+
collection: "config",
|
|
157
|
+
id: this.elementId,
|
|
158
|
+
data: this.cache
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
} catch {
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
notifyListeners() {
|
|
165
|
+
const snapshot = this.getAll();
|
|
166
|
+
for (const listener of this.listeners) {
|
|
167
|
+
try {
|
|
168
|
+
listener(snapshot);
|
|
169
|
+
} catch {
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
function setNestedValue(obj, path, value) {
|
|
175
|
+
const parts = path.split(".");
|
|
176
|
+
let current = obj;
|
|
177
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
178
|
+
const part = parts[i];
|
|
179
|
+
if (!(part in current) || typeof current[part] !== "object" || current[part] === null) {
|
|
180
|
+
current[part] = {};
|
|
181
|
+
}
|
|
182
|
+
current = current[part];
|
|
183
|
+
}
|
|
184
|
+
current[parts[parts.length - 1]] = value;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// src/rtsp-provider.ts
|
|
188
|
+
var RtspProvider = class {
|
|
189
|
+
id;
|
|
190
|
+
type = "rtsp";
|
|
191
|
+
name;
|
|
192
|
+
discoveryMode = "manual";
|
|
193
|
+
ctx;
|
|
194
|
+
devices;
|
|
195
|
+
constructor(config, ctx) {
|
|
196
|
+
this.id = config.id;
|
|
197
|
+
this.name = config.name;
|
|
198
|
+
this.ctx = ctx;
|
|
199
|
+
const built = [];
|
|
200
|
+
for (const cam of config.cameras) {
|
|
201
|
+
const deviceCtx = {
|
|
202
|
+
id: `device:${this.id}/${cam.id}`,
|
|
203
|
+
logger: ctx.logger.child(cam.name),
|
|
204
|
+
eventBus: ctx.eventBus,
|
|
205
|
+
storage: ctx.storage,
|
|
206
|
+
config: new ElementConfigStore(`device:${this.id}/${cam.id}`, ctx.storage)
|
|
207
|
+
};
|
|
208
|
+
built.push(new RtspDevice(cam, this.id, deviceCtx));
|
|
209
|
+
}
|
|
210
|
+
this.devices = built;
|
|
211
|
+
}
|
|
212
|
+
async start() {
|
|
213
|
+
this.ctx.logger.info(`RTSP provider started with ${this.devices.length} cameras`);
|
|
214
|
+
}
|
|
215
|
+
async stop() {
|
|
216
|
+
this.ctx.logger.info("RTSP provider stopped");
|
|
217
|
+
}
|
|
218
|
+
getStatus() {
|
|
219
|
+
return { connected: true, deviceCount: this.devices.length };
|
|
220
|
+
}
|
|
221
|
+
async discoverDevices() {
|
|
222
|
+
return [];
|
|
223
|
+
}
|
|
224
|
+
getDevices() {
|
|
225
|
+
return [...this.devices];
|
|
226
|
+
}
|
|
227
|
+
getDeviceConfigSchema() {
|
|
228
|
+
return {
|
|
229
|
+
id: "string",
|
|
230
|
+
name: "string",
|
|
231
|
+
url: "string (rtsp://...)",
|
|
232
|
+
subStreamUrl: "string (optional)",
|
|
233
|
+
snapshotUrl: "string (optional)"
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
subscribeLiveEvents(_callback) {
|
|
237
|
+
return () => {
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
// src/addon.ts
|
|
243
|
+
var RtspProviderAddon = class {
|
|
244
|
+
manifest = {
|
|
245
|
+
id: "provider-rtsp",
|
|
246
|
+
name: "RTSP Camera Provider",
|
|
247
|
+
version: "0.1.0",
|
|
248
|
+
capabilities: ["device-provider"]
|
|
249
|
+
};
|
|
250
|
+
provider = null;
|
|
251
|
+
async initialize(context) {
|
|
252
|
+
const config = context.addonConfig;
|
|
253
|
+
if (!config.cameras || config.cameras.length === 0) {
|
|
254
|
+
context.logger.info("RTSP provider: no cameras configured");
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
const providerConfig = {
|
|
258
|
+
id: config.id ?? "rtsp-default",
|
|
259
|
+
name: config.name ?? "RTSP Cameras",
|
|
260
|
+
cameras: config.cameras
|
|
261
|
+
};
|
|
262
|
+
this.provider = new RtspProvider(providerConfig, {
|
|
263
|
+
id: context.id,
|
|
264
|
+
logger: context.logger,
|
|
265
|
+
eventBus: context.eventBus,
|
|
266
|
+
storage: context.storage,
|
|
267
|
+
config: context.config
|
|
268
|
+
});
|
|
269
|
+
context.logger.info("RTSP provider addon initialized");
|
|
270
|
+
}
|
|
271
|
+
async shutdown() {
|
|
272
|
+
await this.provider?.stop();
|
|
273
|
+
this.provider = null;
|
|
274
|
+
}
|
|
275
|
+
getCapabilityProvider(name) {
|
|
276
|
+
if (name === "device-provider" && this.provider) {
|
|
277
|
+
return this.provider;
|
|
278
|
+
}
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
getConfigSchema() {
|
|
282
|
+
return {
|
|
283
|
+
sections: [
|
|
284
|
+
{
|
|
285
|
+
id: "general",
|
|
286
|
+
title: "RTSP Provider",
|
|
287
|
+
description: "Configure generic RTSP camera connections",
|
|
288
|
+
columns: 1,
|
|
289
|
+
fields: [
|
|
290
|
+
{ type: "text", key: "name", label: "Provider Name", placeholder: "RTSP Cameras" },
|
|
291
|
+
{
|
|
292
|
+
type: "info",
|
|
293
|
+
key: "info",
|
|
294
|
+
label: "Camera Configuration",
|
|
295
|
+
content: "Individual cameras are configured via the device management interface after adding this provider.",
|
|
296
|
+
variant: "info"
|
|
297
|
+
}
|
|
298
|
+
]
|
|
299
|
+
}
|
|
300
|
+
]
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
getConfig() {
|
|
304
|
+
return {};
|
|
305
|
+
}
|
|
306
|
+
async onConfigChange(_config) {
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
export {
|
|
310
|
+
RtspDevice,
|
|
311
|
+
RtspProvider,
|
|
312
|
+
RtspProviderAddon
|
|
313
|
+
};
|
|
314
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/rtsp-device.ts","../src/element-config-store.ts","../src/rtsp-provider.ts","../src/addon.ts"],"sourcesContent":["import type {\n IDevice,\n DeviceType,\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 = '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 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} from '@camstack/types'\nimport type { 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: readonly 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 config (immutable after construction)\n const built: RtspDevice[] = []\n for (const cam of config.cameras) {\n const deviceCtx: CamstackContext = {\n id: `device:${this.id}/${cam.id}`,\n logger: ctx.logger.child(cam.name),\n eventBus: ctx.eventBus,\n storage: ctx.storage,\n config: new ElementConfigStore(`device:${this.id}/${cam.id}`, ctx.storage),\n }\n built.push(new RtspDevice(cam, this.id, deviceCtx))\n }\n this.devices = built\n }\n\n async start(): Promise<void> {\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() {\n return {\n id: 'string',\n name: 'string',\n url: 'string (rtsp://...)',\n subStreamUrl: 'string (optional)',\n snapshotUrl: 'string (optional)',\n }\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","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 capabilities: ['device-provider'],\n }\n\n private provider: RtspProvider | null = null\n\n async initialize(context: AddonContext): Promise<void> {\n const config = context.addonConfig as unknown as RtspProviderConfig\n\n if (!config.cameras || config.cameras.length === 0) {\n context.logger.info('RTSP provider: no cameras configured')\n return\n }\n\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 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":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@camstack/addon-provider-rtsp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Generic RTSP camera device provider addon for CamStack",
|
|
5
|
+
"keywords": ["camstack", "addon", "camstack-addon", "rtsp", "provider", "camera", "streaming"],
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/camstack/server"
|
|
10
|
+
},
|
|
11
|
+
"main": "./dist/index.js",
|
|
12
|
+
"module": "./dist/index.mjs",
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": { "import": "./dist/index.mjs", "require": "./dist/index.js", "types": "./dist/index.d.ts" }
|
|
16
|
+
},
|
|
17
|
+
"camstack": {
|
|
18
|
+
"addons": [
|
|
19
|
+
{
|
|
20
|
+
"id": "provider-rtsp",
|
|
21
|
+
"entry": "./dist/addon.js",
|
|
22
|
+
"slot": "provider",
|
|
23
|
+
"capabilities": [
|
|
24
|
+
{ "name": "device-provider", "mode": "collection" }
|
|
25
|
+
]
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
"files": ["dist"],
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "tsup",
|
|
32
|
+
"dev": "tsup --watch",
|
|
33
|
+
"typecheck": "tsc --noEmit"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {},
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"@camstack/types": "^0.1.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@camstack/types": "*",
|
|
41
|
+
"tsup": "^8.0.0",
|
|
42
|
+
"typescript": "~5.9.0"
|
|
43
|
+
}
|
|
44
|
+
}
|