@camstack/addon-provider-rtsp 0.1.14 → 0.1.15
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/addon.js +48 -77
- package/dist/addon.js.map +1 -1
- package/dist/addon.mjs +551 -5
- package/dist/addon.mjs.map +1 -1
- package/dist/index.js +6 -587
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +4 -8
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/dist/addon.d.mts +0 -19
- package/dist/addon.d.ts +0 -19
- package/dist/chunk-BXTWJIXO.mjs +0 -560
- package/dist/chunk-BXTWJIXO.mjs.map +0 -1
- package/dist/index.d.mts +0 -83
- package/dist/index.d.ts +0 -83
package/dist/addon.js
CHANGED
|
@@ -1,40 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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/addon.ts
|
|
21
|
-
var addon_exports = {};
|
|
22
|
-
__export(addon_exports, {
|
|
23
|
-
RtspProviderAddon: () => RtspProviderAddon
|
|
24
|
-
});
|
|
25
|
-
module.exports = __toCommonJS(addon_exports);
|
|
26
|
-
var import_types2 = require("@camstack/types");
|
|
27
|
-
|
|
28
|
-
// src/rtsp-camera.ts
|
|
29
|
-
var import_zod = require("zod");
|
|
30
|
-
var import_types = require("@camstack/types");
|
|
31
|
-
var streamEntrySchema = import_zod.z.object({
|
|
32
|
-
id: import_zod.z.string().regex(/^stream-\d+$/).describe("Stable slot id (stream-1/2/3)"),
|
|
33
|
-
url: import_zod.z.string().describe("RTSP stream URL"),
|
|
34
|
-
profileHint: import_zod.z.enum(["high", "mid", "low"]).optional().describe("Provider-suggested profile \u2014 advisory only"),
|
|
35
|
-
resolution: import_zod.z.object({ width: import_zod.z.number().int().positive(), height: import_zod.z.number().int().positive() }).optional().describe("Probed resolution, if known")
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const types = require("@camstack/types");
|
|
4
|
+
const zod = require("zod");
|
|
5
|
+
const streamEntrySchema = zod.z.object({
|
|
6
|
+
id: zod.z.string().regex(/^stream-\d+$/).describe("Stable slot id (stream-1/2/3)"),
|
|
7
|
+
url: zod.z.string().describe("RTSP stream URL"),
|
|
8
|
+
profileHint: zod.z.enum(["high", "mid", "low"]).optional().describe("Provider-suggested profile — advisory only"),
|
|
9
|
+
resolution: zod.z.object({ width: zod.z.number().int().positive(), height: zod.z.number().int().positive() }).optional().describe("Probed resolution, if known")
|
|
36
10
|
});
|
|
37
|
-
|
|
11
|
+
const streamsField = zod.z.preprocess(
|
|
38
12
|
(raw) => {
|
|
39
13
|
if (!Array.isArray(raw)) return raw;
|
|
40
14
|
return raw.map((entry, i) => {
|
|
@@ -51,30 +25,30 @@ var streamsField = import_zod.z.preprocess(
|
|
|
51
25
|
return entry;
|
|
52
26
|
});
|
|
53
27
|
},
|
|
54
|
-
|
|
28
|
+
zod.z.array(streamEntrySchema).min(1)
|
|
55
29
|
);
|
|
56
|
-
|
|
30
|
+
const rtspCameraSchema = zod.z.object({
|
|
57
31
|
// `name` is now base meta (DB column on `device-meta`, not in this
|
|
58
32
|
// hardware-config schema). Read via `this.name` (resolved by
|
|
59
33
|
// `BaseDevice` from `ctx.deviceMeta.name`); mutated via
|
|
60
34
|
// `kernel.devices.setName(id, name)`.
|
|
61
35
|
streams: streamsField.describe("Published RTSP streams"),
|
|
62
|
-
snapshotUrl:
|
|
63
|
-
username:
|
|
64
|
-
password:
|
|
36
|
+
snapshotUrl: zod.z.string().default("").describe("Snapshot URL (optional)"),
|
|
37
|
+
username: zod.z.string().default("").describe("Username"),
|
|
38
|
+
password: zod.z.string().default("").describe("Password")
|
|
65
39
|
});
|
|
66
|
-
|
|
67
|
-
label:
|
|
68
|
-
url:
|
|
40
|
+
const legacyStreamSchema = zod.z.object({
|
|
41
|
+
label: zod.z.enum(["high", "mid", "low"]),
|
|
42
|
+
url: zod.z.string()
|
|
69
43
|
});
|
|
70
|
-
|
|
71
|
-
type =
|
|
44
|
+
class RtspCamera extends types.BaseDevice {
|
|
45
|
+
type = types.DeviceType.Camera;
|
|
72
46
|
features = [];
|
|
73
47
|
constructor(ctx) {
|
|
74
48
|
super(
|
|
75
49
|
ctx,
|
|
76
50
|
rtspCameraSchema,
|
|
77
|
-
{ type:
|
|
51
|
+
{ type: types.DeviceType.Camera }
|
|
78
52
|
);
|
|
79
53
|
this.migrateLegacyStreamsIfNeeded(ctx.persistedConfig ?? {});
|
|
80
54
|
this.registerSnapshotProvider();
|
|
@@ -135,7 +109,7 @@ var RtspCamera = class extends import_types.BaseDevice {
|
|
|
135
109
|
*/
|
|
136
110
|
async onActivate() {
|
|
137
111
|
await this.publishToBroker().catch((err) => {
|
|
138
|
-
this.ctx.logger.warn("publishToBroker on activate failed
|
|
112
|
+
this.ctx.logger.warn("publishToBroker on activate failed — will retry on broker ready", {
|
|
139
113
|
meta: { error: err instanceof Error ? err.message : String(err) }
|
|
140
114
|
});
|
|
141
115
|
});
|
|
@@ -210,7 +184,7 @@ var RtspCamera = class extends import_types.BaseDevice {
|
|
|
210
184
|
{
|
|
211
185
|
id: "streams",
|
|
212
186
|
title: "RTSP Streams",
|
|
213
|
-
description: "Provide 1
|
|
187
|
+
description: "Provide 1–3 RTSP URLs for this camera. After save, each URL is probed and the stream-broker assigns the (high / mid / low) profiles by resolution (highest pixel count → high). Input slot order in this form does not affect the broker profile mapping.",
|
|
214
188
|
columns: 1,
|
|
215
189
|
fields: [
|
|
216
190
|
{
|
|
@@ -250,7 +224,7 @@ var RtspCamera = class extends import_types.BaseDevice {
|
|
|
250
224
|
}
|
|
251
225
|
]
|
|
252
226
|
};
|
|
253
|
-
return
|
|
227
|
+
return types.hydrateSchema(schema, this.collectFormValues());
|
|
254
228
|
}
|
|
255
229
|
/**
|
|
256
230
|
* Project stored config into the flat UI keys referenced by the schema.
|
|
@@ -299,7 +273,7 @@ var RtspCamera = class extends import_types.BaseDevice {
|
|
|
299
273
|
const streamProbe = this.ctx.streamProbe;
|
|
300
274
|
if (!streamProbe) {
|
|
301
275
|
this.ctx.logger.warn(
|
|
302
|
-
"[rtsp-edit] ctx.streamProbe unavailable
|
|
276
|
+
"[rtsp-edit] ctx.streamProbe unavailable — falling back to input-order profile assignment",
|
|
303
277
|
{ meta: { streamCount: urls.length } }
|
|
304
278
|
);
|
|
305
279
|
const sequence = urls.length === 1 ? ["high"] : urls.length === 2 ? ["high", "low"] : ["high", "mid", "low"];
|
|
@@ -316,15 +290,15 @@ var RtspCamera = class extends import_types.BaseDevice {
|
|
|
316
290
|
return { url, metadata };
|
|
317
291
|
} catch (err) {
|
|
318
292
|
this.ctx.logger.warn(
|
|
319
|
-
"[rtsp-edit] probe failed
|
|
320
|
-
{ meta: { url:
|
|
293
|
+
"[rtsp-edit] probe failed — landing in the lowest tier via classifyStreams",
|
|
294
|
+
{ meta: { url: types.maskUrlCredentials(url), error: err instanceof Error ? err.message : String(err) } }
|
|
321
295
|
);
|
|
322
296
|
const emptyMetadata = {};
|
|
323
297
|
return { url, metadata: emptyMetadata };
|
|
324
298
|
}
|
|
325
299
|
})
|
|
326
300
|
);
|
|
327
|
-
const classified =
|
|
301
|
+
const classified = types.classifyStreams(probed);
|
|
328
302
|
const metadataByUrl = new Map(probed.map((p) => [p.url, p.metadata]));
|
|
329
303
|
const classifiedByUrl = new Map(classified.map((c) => [c.url, c]));
|
|
330
304
|
return urls.map((url, i) => {
|
|
@@ -352,7 +326,7 @@ var RtspCamera = class extends import_types.BaseDevice {
|
|
|
352
326
|
invalidateCache: async () => {
|
|
353
327
|
}
|
|
354
328
|
};
|
|
355
|
-
this.ctx.registerNativeCap(
|
|
329
|
+
this.ctx.registerNativeCap(types.snapshotCapability, provider);
|
|
356
330
|
}
|
|
357
331
|
async fetchHttpSnapshot(url) {
|
|
358
332
|
const username = (this.config.get("username") ?? "").trim();
|
|
@@ -367,9 +341,7 @@ var RtspCamera = class extends import_types.BaseDevice {
|
|
|
367
341
|
const ab = await res.arrayBuffer();
|
|
368
342
|
return Buffer.from(ab);
|
|
369
343
|
}
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
// src/addon.ts
|
|
344
|
+
}
|
|
373
345
|
function getString(obj, key) {
|
|
374
346
|
const v = obj[key];
|
|
375
347
|
return typeof v === "string" ? v : "";
|
|
@@ -393,7 +365,7 @@ function buildCreationFormSchema() {
|
|
|
393
365
|
{
|
|
394
366
|
id: "streams",
|
|
395
367
|
title: "RTSP Streams",
|
|
396
|
-
description: "Provide 1
|
|
368
|
+
description: "Provide 1–3 RTSP URLs for this camera. Each URL is probed and published to the stream-broker; the broker assigns the (high / mid / low) profiles by resolution — you do not need to label the streams manually.",
|
|
397
369
|
columns: 1,
|
|
398
370
|
fields: [
|
|
399
371
|
{
|
|
@@ -439,11 +411,11 @@ function isStreamBrokerReady(event) {
|
|
|
439
411
|
const data = event.data;
|
|
440
412
|
return data["capName"] === "stream-broker" && data["state"] === "ready";
|
|
441
413
|
}
|
|
442
|
-
|
|
414
|
+
class RtspProviderAddon extends types.BaseDeviceProvider {
|
|
443
415
|
addonId = "provider-rtsp";
|
|
444
416
|
providerName = "RTSP";
|
|
445
417
|
deviceClasses = {
|
|
446
|
-
[
|
|
418
|
+
[types.DeviceType.Camera]: RtspCamera
|
|
447
419
|
};
|
|
448
420
|
constructor() {
|
|
449
421
|
super({});
|
|
@@ -451,7 +423,7 @@ var RtspProviderAddon = class extends import_types2.BaseDeviceProvider {
|
|
|
451
423
|
async onInitialize() {
|
|
452
424
|
const regs = await super.onInitialize();
|
|
453
425
|
this.subscribe(
|
|
454
|
-
{ category:
|
|
426
|
+
{ category: types.EventCategory.DeviceStateChanged },
|
|
455
427
|
(event) => {
|
|
456
428
|
const data = event.data;
|
|
457
429
|
if (data.capName !== "camera-streams") return;
|
|
@@ -467,7 +439,7 @@ var RtspProviderAddon = class extends import_types2.BaseDeviceProvider {
|
|
|
467
439
|
}
|
|
468
440
|
);
|
|
469
441
|
this.subscribe(
|
|
470
|
-
{ category:
|
|
442
|
+
{ category: types.EventCategory.SystemReadyState },
|
|
471
443
|
(event) => {
|
|
472
444
|
if (!isStreamBrokerReady(event)) return;
|
|
473
445
|
void this.republishAll().catch((err) => {
|
|
@@ -481,11 +453,11 @@ var RtspProviderAddon = class extends import_types2.BaseDeviceProvider {
|
|
|
481
453
|
}
|
|
482
454
|
// ── Creation ─────────────────────────────────────────────────────────
|
|
483
455
|
async onGetCreationSchema(type) {
|
|
484
|
-
if (type !==
|
|
456
|
+
if (type !== types.DeviceType.Camera) return null;
|
|
485
457
|
return buildCreationFormSchema();
|
|
486
458
|
}
|
|
487
459
|
async onCreateDevice(type, config) {
|
|
488
|
-
if (type !==
|
|
460
|
+
if (type !== types.DeviceType.Camera) {
|
|
489
461
|
throw new Error(`RTSP provider does not support device type: ${type}`);
|
|
490
462
|
}
|
|
491
463
|
const name = getString(config, "name").trim();
|
|
@@ -494,7 +466,7 @@ var RtspProviderAddon = class extends import_types2.BaseDeviceProvider {
|
|
|
494
466
|
if (rawStreams.length === 0) throw new Error("At least one RTSP stream URL is required");
|
|
495
467
|
if (rawStreams.length > 3) throw new Error("At most 3 RTSP stream URLs are supported");
|
|
496
468
|
const streamProbe = this.ctx.kernel.streamProbe;
|
|
497
|
-
if (!streamProbe) throw new Error("ctx.kernel.streamProbe unavailable
|
|
469
|
+
if (!streamProbe) throw new Error("ctx.kernel.streamProbe unavailable — cannot probe streams");
|
|
498
470
|
const probed = await Promise.all(
|
|
499
471
|
rawStreams.map(async (url) => {
|
|
500
472
|
try {
|
|
@@ -502,14 +474,14 @@ var RtspProviderAddon = class extends import_types2.BaseDeviceProvider {
|
|
|
502
474
|
return { url, metadata, ok: true };
|
|
503
475
|
} catch (err) {
|
|
504
476
|
this.ctx.logger.warn(
|
|
505
|
-
"[rtsp-create] probe failed
|
|
506
|
-
{ meta: { url:
|
|
477
|
+
"[rtsp-create] probe failed — publishing without probed metadata",
|
|
478
|
+
{ meta: { url: types.maskUrlCredentials(url), error: err instanceof Error ? err.message : String(err) } }
|
|
507
479
|
);
|
|
508
480
|
return { url, metadata: void 0, ok: false };
|
|
509
481
|
}
|
|
510
482
|
})
|
|
511
483
|
);
|
|
512
|
-
const classified =
|
|
484
|
+
const classified = types.classifyStreams(
|
|
513
485
|
probed.map((p) => ({ url: p.url, metadata: p.metadata ?? {} }))
|
|
514
486
|
);
|
|
515
487
|
const hintByUrl = new Map(classified.map((c) => [c.url, c.quality]));
|
|
@@ -529,18 +501,18 @@ var RtspProviderAddon = class extends import_types2.BaseDeviceProvider {
|
|
|
529
501
|
password: getString(config, "password")
|
|
530
502
|
});
|
|
531
503
|
return {
|
|
532
|
-
meta: { type:
|
|
504
|
+
meta: { type: types.DeviceType.Camera, name },
|
|
533
505
|
config: parsed
|
|
534
506
|
};
|
|
535
507
|
}
|
|
536
508
|
// ── Field probing ────────────────────────────────────────────────────
|
|
537
509
|
async testCreationField(input) {
|
|
538
|
-
if (input.type !==
|
|
510
|
+
if (input.type !== types.DeviceType.Camera) {
|
|
539
511
|
return { status: "error", error: `Unsupported device type: ${input.type}` };
|
|
540
512
|
}
|
|
541
513
|
const streamProbe = this.ctx.kernel.streamProbe;
|
|
542
514
|
if (!streamProbe) {
|
|
543
|
-
return { status: "error", error: "ctx.kernel.streamProbe unavailable
|
|
515
|
+
return { status: "error", error: "ctx.kernel.streamProbe unavailable — cannot reach kernel probe" };
|
|
544
516
|
}
|
|
545
517
|
return streamProbe.probeField(input.key, input.value);
|
|
546
518
|
}
|
|
@@ -574,9 +546,8 @@ var RtspProviderAddon = class extends import_types2.BaseDeviceProvider {
|
|
|
574
546
|
});
|
|
575
547
|
}
|
|
576
548
|
}
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
//# sourceMappingURL=addon.js.map
|
|
549
|
+
}
|
|
550
|
+
exports.RtspCamera = RtspCamera;
|
|
551
|
+
exports.RtspProviderAddon = RtspProviderAddon;
|
|
552
|
+
exports.rtspCameraSchema = rtspCameraSchema;
|
|
553
|
+
//# sourceMappingURL=addon.js.map
|
package/dist/addon.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/addon.ts","../src/rtsp-camera.ts"],"sourcesContent":["import type {\n ConfigUISchema,\n CreateDeviceSpec,\n DeviceConstructor,\n FieldProbeResult,\n IDevice,\n ProviderRegistration,\n StreamQuality,\n SystemEvent,\n} from '@camstack/types'\nimport { BaseDeviceProvider, DeviceType, classifyStreams, EventCategory, maskUrlCredentials } from '@camstack/types'\nimport { RtspCamera, rtspCameraSchema, type RtspStreamEntry } from './rtsp-camera.js'\n\nfunction getString(obj: Record<string, unknown>, key: string): string {\n const v = obj[key]\n return typeof v === 'string' ? v : ''\n}\n\nfunction getStringArray(obj: Record<string, unknown>, key: string): readonly string[] {\n const v = obj[key]\n if (!Array.isArray(v)) return []\n return v\n .filter((s): s is string => typeof s === 'string')\n .map((s) => s.trim())\n .filter((s) => s.length > 0)\n}\n\n/**\n * Build the creation form schema. Hand-written ConfigUISchema instead of\n * converting from Zod — this lets us expose probe-typed fields (live\n * ffprobe \"Test\" button) that the automatic converter doesn't emit.\n *\n * `streams` is a single probe field rendered as a `multiple` array —\n * 1..3 instances; the backend probes each via the kernel's\n * `StreamProbeService` and publishes them to the stream-broker, which\n * decides the (high / mid / low) profile mapping by resolution.\n */\nfunction buildCreationFormSchema(): ConfigUISchema {\n return {\n sections: [\n {\n id: 'identity',\n title: 'Camera',\n columns: 1,\n fields: [\n { type: 'text', key: 'name', label: 'Name', required: true, placeholder: 'Living room' },\n ],\n },\n {\n id: 'streams',\n title: 'RTSP Streams',\n description: 'Provide 1–3 RTSP URLs for this camera. Each URL is probed and published to the stream-broker; the broker assigns the (high / mid / low) profiles by resolution — you do not need to label the streams manually.',\n columns: 1,\n fields: [\n {\n type: 'probe',\n key: 'streams',\n label: 'RTSP stream URL',\n required: true,\n placeholder: 'rtsp://user:pass@host:554/Streaming/Channels/101',\n inputType: 'url',\n multiple: {\n min: 1,\n max: 3,\n addLabel: 'Add stream',\n itemLabel: 'Stream ${n}',\n itemDefault: '',\n },\n },\n ],\n },\n {\n id: 'snapshot',\n title: 'Snapshot',\n columns: 1,\n fields: [\n { type: 'probe', key: 'snapshotUrl', label: 'Snapshot URL', description: 'Optional HTTP(S) endpoint that returns a JPEG still.', placeholder: 'http://host/ISAPI/Streaming/channels/101/picture', inputType: 'url' },\n ],\n },\n {\n id: 'credentials',\n title: 'Credentials',\n description: 'Optional. If the RTSP URL already contains user:pass@ you can leave these empty.',\n columns: 2,\n fields: [\n { type: 'text', key: 'username', label: 'Username' },\n { type: 'password', key: 'password', label: 'Password', showToggle: true },\n ],\n },\n ],\n }\n}\n\n/**\n * Re-publish every RtspCamera's streams to the broker whenever the\n * broker emits `system.ready-state` state=ready. Handles boot-order\n * races (broker starts after provider) and broker restart recovery.\n * At normal runtime, `onCreateDevice` publishes eagerly so new cameras\n * don't have to wait for the next ready fire.\n */\nfunction isStreamBrokerReady(event: SystemEvent): boolean {\n if (event.category !== 'system.ready-state') return false\n const data = event.data as Record<string, unknown>\n return data['capName'] === 'stream-broker' && data['state'] === 'ready'\n}\n\nexport class RtspProviderAddon extends BaseDeviceProvider {\n protected readonly addonId = 'provider-rtsp'\n protected readonly providerName = 'RTSP'\n protected readonly deviceClasses: Partial<Record<DeviceType, DeviceConstructor<IDevice>>> = {\n [DeviceType.Camera]: RtspCamera,\n }\n\n constructor() { super({}) }\n\n protected override async onInitialize(): Promise<ProviderRegistration[]> {\n const regs = await super.onInitialize()\n // Autonomous online tracking — mirror `state.cameraStreams.online`\n // (written by the stream-broker on every health flip) into\n // `device.online` for every RTSP-owned device. RTSP has no firmware\n // liveness signal, so the broker's view is the canonical source.\n // Reolink, which has its own firmware push events, doesn't subscribe.\n this.subscribe(\n { category: EventCategory.DeviceStateChanged },\n (event) => {\n const data = event.data as { deviceId?: number; capName?: string; slice?: { online?: boolean } }\n if (data.capName !== 'camera-streams') return\n const deviceId = data.deviceId\n if (typeof deviceId !== 'number') return\n const registry = this.ctx.kernel.deviceRegistry\n if (!registry) return\n if (registry.getAddonId(deviceId) !== this.addonId) return\n const device = registry.getById(deviceId)\n if (!device) return\n const online = data.slice?.online === true\n if (device.online !== online) device.online = online\n },\n )\n this.subscribe(\n { category: EventCategory.SystemReadyState },\n (event) => {\n if (!isStreamBrokerReady(event)) return\n void this.republishAll().catch((err: unknown) => {\n this.ctx.logger.warn('Failed to re-publish RTSP streams after broker ready', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n })\n },\n )\n return regs\n }\n\n // ── Creation ─────────────────────────────────────────────────────────\n\n protected async onGetCreationSchema(type: DeviceType): Promise<ConfigUISchema | null> {\n if (type !== DeviceType.Camera) return null\n return buildCreationFormSchema()\n }\n\n protected async onCreateDevice(type: DeviceType, config: Record<string, unknown>): Promise<CreateDeviceSpec> {\n if (type !== DeviceType.Camera) {\n throw new Error(`RTSP provider does not support device type: ${type}`)\n }\n\n const name = getString(config, 'name').trim()\n if (!name) throw new Error('Camera name is required')\n\n const rawStreams = getStringArray(config, 'streams')\n if (rawStreams.length === 0) throw new Error('At least one RTSP stream URL is required')\n if (rawStreams.length > 3) throw new Error('At most 3 RTSP stream URLs are supported')\n\n const streamProbe = this.ctx.kernel.streamProbe\n if (!streamProbe) throw new Error('ctx.kernel.streamProbe unavailable — cannot probe streams')\n const probed = await Promise.all(\n rawStreams.map(async (url) => {\n try {\n const metadata = await streamProbe.probe(url, { force: false })\n return { url, metadata, ok: true as const }\n } catch (err) {\n this.ctx.logger.warn(\n '[rtsp-create] probe failed — publishing without probed metadata',\n { meta: { url: maskUrlCredentials(url), error: err instanceof Error ? err.message : String(err) } },\n )\n return { url, metadata: undefined, ok: false as const }\n }\n }),\n )\n\n const classified = classifyStreams(\n probed.map((p) => ({ url: p.url, metadata: p.metadata ?? {} })),\n )\n const hintByUrl = new Map<string, StreamQuality>(classified.map((c) => [c.url, c.quality]))\n\n const streams: RtspStreamEntry[] = probed.map((p, i): RtspStreamEntry => {\n const entry: RtspStreamEntry = { id: `stream-${i + 1}`, url: p.url }\n const hint = hintByUrl.get(p.url)\n if (hint) entry.profileHint = hint\n if (p.metadata?.width && p.metadata?.height) {\n entry.resolution = { width: p.metadata.width, height: p.metadata.height }\n }\n return entry\n })\n\n const parsed = rtspCameraSchema.parse({\n streams,\n snapshotUrl: getString(config, 'snapshotUrl'),\n username: getString(config, 'username'),\n password: getString(config, 'password'),\n })\n\n return {\n meta: { type: DeviceType.Camera, name },\n config: parsed,\n }\n }\n\n // ── Field probing ────────────────────────────────────────────────────\n\n override async testCreationField(input: {\n type: DeviceType\n key: string\n value: unknown\n }): Promise<FieldProbeResult> {\n if (input.type !== DeviceType.Camera) {\n return { status: 'error', error: `Unsupported device type: ${input.type}` }\n }\n const streamProbe = this.ctx.kernel.streamProbe\n if (!streamProbe) {\n return { status: 'error', error: 'ctx.kernel.streamProbe unavailable — cannot reach kernel probe' }\n }\n return streamProbe.probeField(input.key, input.value)\n }\n\n // ── Restore ──────────────────────────────────────────────────────────\n //\n // `BaseDeviceProvider.onRestoreDevices` default impl iterates\n // `savedDevices`, looks up the right class via `deviceClasses`,\n // and calls `kernel.devices.create()`. Each created device fires\n // its `onCreated` lifecycle hook (see `RtspCamera.onCreated()`)\n // which publishes streams to the broker — no per-provider override\n // needed here.\n\n // ── Internal — re-publish every RtspCamera to the broker ────────────\n\n private async republishAll(): Promise<void> {\n const all = (await this.ctx.kernel.devices?.getAll()) ?? []\n let published = 0\n for (const dev of all as readonly IDevice[]) {\n if (!(dev instanceof RtspCamera)) continue\n try {\n await dev.publishToBroker()\n published++\n } catch (err) {\n this.ctx.logger.debug('publishToBroker threw during republish', {\n tags: { deviceId: dev.id },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n }\n }\n if (published > 0) {\n this.ctx.logger.info('Re-published RTSP streams to stream-broker', {\n meta: { published, total: all.length },\n })\n }\n }\n}\n","import { z } from \"zod\";\nimport { BaseDevice, DeviceType, DeviceFeature, classifyStreams, hydrateSchema, snapshotCapability, maskUrlCredentials } from \"@camstack/types\";\nimport type {\n ConfigUISchema,\n ConfigUISchemaWithValues,\n DeviceContext,\n ICameraDevice,\n InferNativeProvider,\n StreamMetadata,\n StreamQuality,\n StreamSourceEntry,\n} from \"@camstack/types\";\n\n/**\n * Stable, provider-assigned ids within a (deviceId) scope.\n *\n * `stream-1/2/3` map 1:1 with the operator-visible input slots in the\n * creation form. Once assigned at create time (or via legacy migration)\n * the id is permanent — editing a URL keeps its slot. Separating the id\n * from the quality lets the stream-broker decide the (high/mid/low)\n * mapping from probed metadata without the driver having to re-label.\n */\nconst streamEntrySchema = z.object({\n id: z.string().regex(/^stream-\\d+$/).describe(\"Stable slot id (stream-1/2/3)\"),\n url: z.string().describe(\"RTSP stream URL\"),\n profileHint: z.enum(['high', 'mid', 'low']).optional().describe(\"Provider-suggested profile — advisory only\"),\n resolution: z\n .object({ width: z.number().int().positive(), height: z.number().int().positive() })\n .optional()\n .describe(\"Probed resolution, if known\"),\n});\n\nexport type RtspStreamEntry = z.infer<typeof streamEntrySchema>\n\n/**\n * Accepts both the new `{id, url, profileHint?, resolution?}` shape AND\n * the legacy `{label, url}` shape. Legacy entries get rewritten to the\n * new shape at parse time so the rest of the driver can treat storage\n * uniformly. The constructor then persists the new shape back to disk\n * in `migrateLegacyStreamsIfNeeded` so subsequent parses are already\n * on the new shape.\n */\nconst streamsField = z.preprocess(\n (raw: unknown): unknown => {\n if (!Array.isArray(raw)) return raw\n return (raw as readonly unknown[]).map((entry, i): unknown => {\n if (!entry || typeof entry !== 'object') return entry\n const o = entry as Record<string, unknown>\n if (typeof o['id'] === 'string') return entry\n if (typeof o['label'] === 'string' && typeof o['url'] === 'string') {\n return {\n id: `stream-${i + 1}`,\n url: o['url'],\n profileHint: o['label'],\n }\n }\n return entry\n })\n },\n z.array(streamEntrySchema).min(1),\n)\n\nexport const rtspCameraSchema = z.object({\n // `name` is now base meta (DB column on `device-meta`, not in this\n // hardware-config schema). Read via `this.name` (resolved by\n // `BaseDevice` from `ctx.deviceMeta.name`); mutated via\n // `kernel.devices.setName(id, name)`.\n streams: streamsField.describe(\"Published RTSP streams\"),\n snapshotUrl: z.string().default(\"\").describe(\"Snapshot URL (optional)\"),\n username: z.string().default(\"\").describe(\"Username\"),\n password: z.string().default(\"\").describe(\"Password\"),\n});\n\n/**\n * Legacy storage shape: `[{ label: 'high'|'mid'|'low', url }]`.\n * Migrated to the new `[{id, url, profileHint}]` shape on construct\n * via `config.setAll`. The conversion is permanent — after one boot\n * every camera row is on the new shape.\n */\nconst legacyStreamSchema = z.object({\n label: z.enum(['high', 'mid', 'low']),\n url: z.string(),\n})\n\nexport class RtspCamera\n extends BaseDevice<typeof rtspCameraSchema>\n implements ICameraDevice\n{\n readonly type = DeviceType.Camera as const;\n features = [] as const satisfies readonly DeviceFeature[];\n\n constructor(ctx: DeviceContext) {\n super(\n ctx,\n rtspCameraSchema,\n { type: DeviceType.Camera },\n );\n // Online state is now firmware/broker-event driven only — BaseDevice\n // seeds `device-status` slice with online=false, and the provider's\n // stream-health aggregator (`BaseDeviceProvider.subscribeStreamHealth`)\n // flips it true on the first `stream.online` event.\n this.migrateLegacyStreamsIfNeeded(ctx.persistedConfig ?? {})\n this.registerSnapshotProvider();\n }\n\n /**\n * Detect legacy `{label, url}` stream shapes and reshape to\n * `{id, url, profileHint}`. Persists via `config.setAll` once, so\n * subsequent boots find the new shape and this branch is inert.\n *\n * Runs fire-and-forget — the constructor can't await. The reshape is\n * additive (ids are newly minted) so a failed persist is recoverable\n * on next boot.\n */\n private migrateLegacyStreamsIfNeeded(initialData: Record<string, unknown>): void {\n const raw = initialData['streams']\n if (!Array.isArray(raw)) return\n // All entries already have `id` → new shape; nothing to do.\n if (raw.every((s) => s && typeof s === 'object' && typeof (s as Record<string, unknown>)['id'] === 'string')) {\n return\n }\n const migrated: RtspStreamEntry[] = []\n let nextIdx = 1\n for (const entry of raw) {\n const parsed = legacyStreamSchema.safeParse(entry)\n if (!parsed.success) continue\n migrated.push({\n id: `stream-${nextIdx++}`,\n url: parsed.data.url,\n profileHint: parsed.data.label,\n })\n }\n if (migrated.length === 0) return\n const snapshotUrl = this.config.get('snapshotUrl') ?? ''\n const username = this.config.get('username') ?? ''\n const password = this.config.get('password') ?? ''\n this.ctx.logger.info('Migrating legacy {label,url} streams to {id,url,profileHint}', {\n tags: { stableId: this.stableId },\n meta: { streamCount: migrated.length },\n })\n void this.config\n .setAll({ streams: migrated, snapshotUrl, username, password })\n .catch((err: unknown) => {\n this.ctx.logger.warn('Legacy stream migration failed', {\n tags: { stableId: this.stableId },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n })\n }\n\n /**\n * Publish every configured stream to the system stream-broker as a\n * `pull-rtsp` cam stream. Idempotent — the broker's publishCameraStream\n * entry is keyed by `(deviceId, camStreamId)` so callers may invoke\n * this at will (e.g. on stream-broker restart). Fire-and-forget safe.\n */\n /**\n * Lifecycle hook fired by the kernel after registration. Publishes\n * the camera's streams to the broker — the kernel doesn't know\n * about the broker, but the camera does. Best-effort: if the\n * broker isn't ready, the provider's `system.ready-state`\n * subscription re-publishes on the next ready fire.\n */\n override async onActivate(): Promise<void> {\n await this.publishToBroker().catch((err: unknown) => {\n this.ctx.logger.warn('publishToBroker on activate failed — will retry on broker ready', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n })\n }\n\n async publishToBroker(): Promise<void> {\n const streams = this.config.get('streams')\n for (const [i, s] of streams.entries()) {\n try {\n await this.ctx.api.streamBroker.publishCameraStream.mutate({\n deviceId: this.id,\n camStreamId: s.id,\n kind: 'pull-rtsp',\n url: s.url,\n ...(s.resolution ? { resolution: s.resolution } : {}),\n label: `Stream ${i + 1}`,\n })\n } catch (err: unknown) {\n this.ctx.logger.warn('publishCameraStream failed', {\n tags: { deviceId: this.id, camStreamId: s.id },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n }\n }\n }\n\n /**\n * Thin compat shim: reflects the stored `{id, url, profileHint?}`\n * streams into the legacy `StreamSourceEntry` shape so\n * `ICameraDevice`-aware consumers (device-manager legacy paths,\n * device-cap-proxy fallback) still find data. The broker itself does\n * NOT read this anymore — it consumes `publishCameraStream`. This\n * shim goes away when every consumer migrates (C5/C7).\n */\n async getStreamSources(): Promise<readonly StreamSourceEntry[]> {\n const streams = this.config.get('streams')\n return streams.map((s): StreamSourceEntry => ({\n id: s.id,\n label: s.profileHint ?? s.id,\n protocol: 'rtsp' as const,\n url: s.url,\n ...(s.profileHint ? { profileHint: s.profileHint } : {}),\n ...(s.resolution ? { resolution: s.resolution } : {}),\n }))\n }\n\n override async removeDevice(): Promise<void> {\n this.ctx.logger.info('Removing RTSP camera', { meta: { stableId: this.stableId } });\n const streams = this.config.get('streams')\n await Promise.all(\n streams.map((s) =>\n this.ctx.api.streamBroker.retractCameraStream\n .mutate({ deviceId: this.id, camStreamId: s.id })\n .catch((err: unknown) => {\n this.ctx.logger.warn('retractCameraStream failed', {\n tags: { deviceId: this.id, camStreamId: s.id },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n }),\n ),\n )\n }\n\n // ── Driver-authored settings UI ────────────────────────────────────────\n //\n // Streams are stored as `Array<{id, url, profileHint?, resolution?}>`\n // but the settings UI exposes them as a `multiple` probe field of raw\n // URLs — the form only cares about the ordered list of URLs, the ids\n // and classification live in storage. Values are projected from\n // storage into the flat form keys referenced by the schema.\n\n override getSettingsUISchema(): ConfigUISchemaWithValues {\n const schema: ConfigUISchema = {\n sections: [\n // The `name` field is now base meta — rendered by the\n // device-manager's own settings contribution (with the\n // location field), NOT by the driver's hardware-config\n // schema. Driver UIs only carry technical knobs.\n {\n id: 'streams',\n title: 'RTSP Streams',\n description: 'Provide 1–3 RTSP URLs for this camera. After save, each URL is probed and the stream-broker assigns the (high / mid / low) profiles by resolution (highest pixel count → high). Input slot order in this form does not affect the broker profile mapping.',\n columns: 1,\n fields: [\n {\n type: 'probe',\n key: 'streams',\n label: 'RTSP stream URL',\n required: true,\n placeholder: 'rtsp://user:pass@host:554/Streaming/Channels/101',\n inputType: 'url',\n multiple: {\n min: 1,\n max: 3,\n addLabel: 'Add stream',\n itemLabel: 'Stream ${n}',\n itemDefault: '',\n },\n },\n ],\n },\n {\n id: 'snapshot',\n title: 'Snapshot',\n columns: 1,\n fields: [\n { type: 'probe', key: 'snapshotUrl', label: 'Snapshot URL', description: 'Optional HTTP(S) endpoint that returns a JPEG still.', placeholder: 'http://host/ISAPI/Streaming/channels/101/picture', inputType: 'url' },\n ],\n },\n {\n id: 'credentials',\n title: 'Credentials',\n description: 'Optional. If the RTSP URL already contains user:pass@ you can leave these empty.',\n columns: 2,\n fields: [\n { type: 'text', key: 'username', label: 'Username' },\n { type: 'password', key: 'password', label: 'Password', showToggle: true },\n ],\n },\n ],\n }\n return hydrateSchema(schema, this.collectFormValues())\n }\n\n /**\n * Project stored config into the flat UI keys referenced by the schema.\n * Form only sees URLs — the internal id/profileHint/resolution stay\n * invisible to the operator (the broker decides profiles from probed\n * metadata at save time).\n */\n private collectFormValues(): Record<string, unknown> {\n const stored = this.config.get('streams')\n // Preserve the slot order from storage (`stream-1`, `stream-2`, …).\n const sorted = [...stored].sort((a, b) => a.id.localeCompare(b.id, 'en', { numeric: true }))\n const urls = sorted.map((s) => s.url).filter((u) => u.length > 0)\n return {\n streams: urls,\n snapshotUrl: this.config.get('snapshotUrl'),\n username: this.config.get('username'),\n password: this.config.get('password'),\n }\n }\n\n override async applySettingsPatch(patch: Record<string, unknown>): Promise<void> {\n const current = this.collectFormValues()\n const merged: Record<string, unknown> = { ...current, ...patch }\n const pickStr = (k: string): string => (typeof merged[k] === 'string' ? (merged[k] as string).trim() : '')\n\n const next: Record<string, unknown> = {}\n\n const rawStreams = merged['streams']\n const incomingUrls: string[] = Array.isArray(rawStreams)\n ? rawStreams\n .filter((s): s is string => typeof s === 'string')\n .map((s) => s.trim())\n .filter((s) => s.length > 0)\n : []\n\n if (incomingUrls.length === 0) {\n // UI cleared every slot — keep current streams so zod's min(1)\n // invariant holds and the camera keeps streaming until the next\n // save with real URLs.\n next.streams = this.config.get('streams')\n } else {\n const probed = await this.probeAndClassify(incomingUrls.slice(0, 3))\n next.streams = probed\n }\n\n next.snapshotUrl = pickStr('snapshotUrl')\n next.username = pickStr('username')\n next.password = typeof merged['password'] === 'string' ? merged['password'] : this.config.get('password')\n\n await this.config.setAll(next)\n\n // Storage now has the new stream list — re-publish so the broker\n // sees any URL/resolution changes. `publishCameraStream` is\n // idempotent for the same camStreamId so slots that didn't change\n // are free no-ops.\n await this.publishToBroker()\n }\n\n /**\n * Probe a list of RTSP URLs through the kernel stream-probe and return\n * the new `{id, url, profileHint?, resolution?}` entries. Ids are\n * assigned `stream-1/2/3` in input order so the UI's slot ordering is\n * preserved across edits. The broker is in charge of the actual\n * (high/mid/low) profile assignment via `computeInitialAssignment`.\n */\n private async probeAndClassify(urls: readonly string[]): Promise<RtspStreamEntry[]> {\n const streamProbe = this.ctx.streamProbe\n if (!streamProbe) {\n this.ctx.logger.warn(\n '[rtsp-edit] ctx.streamProbe unavailable — falling back to input-order profile assignment',\n { meta: { streamCount: urls.length } },\n )\n const sequence: ReadonlyArray<StreamQuality> =\n urls.length === 1 ? ['high']\n : urls.length === 2 ? ['high', 'low']\n : ['high', 'mid', 'low']\n return urls.map((url, i): RtspStreamEntry => ({\n id: `stream-${i + 1}`,\n url,\n profileHint: sequence[i]!,\n }))\n }\n\n const probed = await Promise.all(\n urls.map(async (url) => {\n try {\n const metadata = await streamProbe.probe(url, { force: false })\n return { url, metadata }\n } catch (err) {\n this.ctx.logger.warn(\n '[rtsp-edit] probe failed — landing in the lowest tier via classifyStreams',\n { meta: { url: maskUrlCredentials(url), error: err instanceof Error ? err.message : String(err) } },\n )\n const emptyMetadata: StreamMetadata = {}\n return { url, metadata: emptyMetadata }\n }\n }),\n )\n const classified = classifyStreams(probed)\n // Each classified entry carries a quality tag. We preserve the\n // input order for the slot id and use the tag as `profileHint`;\n // resolution comes from the probed metadata (matched by url).\n const metadataByUrl = new Map(probed.map((p) => [p.url, p.metadata]))\n const classifiedByUrl = new Map(classified.map((c) => [c.url, c]))\n return urls.map((url, i): RtspStreamEntry => {\n const tag = classifiedByUrl.get(url)\n const meta = metadataByUrl.get(url)\n const entry: RtspStreamEntry = { id: `stream-${i + 1}`, url }\n if (tag) entry.profileHint = tag.quality\n if (meta?.width && meta?.height) entry.resolution = { width: meta.width, height: meta.height }\n return entry\n })\n }\n\n // ── Native capabilities ────────────────────────────────────────────────\n\n private registerSnapshotProvider(): void {\n const snapshotUrl = (this.config.get(\"snapshotUrl\") ?? \"\").trim();\n if (!snapshotUrl) return;\n\n const provider: InferNativeProvider<typeof snapshotCapability> = {\n getSnapshot: async ({ deviceId }) => {\n if (deviceId !== this.id) {\n throw new Error(`RtspCamera: deviceId mismatch, expected ${this.id}, got ${deviceId}`);\n }\n const buf = await this.fetchHttpSnapshot(snapshotUrl);\n if (buf.length === 0) return null;\n return { base64: buf.toString(\"base64\"), contentType: \"image/jpeg\" };\n },\n invalidateCache: async () => {\n /* no-op — caching is the snapshot orchestrator's concern */\n },\n };\n this.ctx.registerNativeCap(snapshotCapability, provider);\n }\n\n private async fetchHttpSnapshot(url: string): Promise<Buffer> {\n const username = (this.config.get(\"username\") ?? \"\").trim();\n const password = (this.config.get(\"password\") ?? \"\").trim();\n const headers: Record<string, string> = {};\n if (username || password) {\n const creds = Buffer.from(`${username}:${password}`).toString(\"base64\");\n headers[\"authorization\"] = `Basic ${creds}`;\n }\n const res = await fetch(url, { headers });\n if (!res.ok) throw new Error(`HTTP ${res.status} ${res.statusText}`);\n const ab = await res.arrayBuffer();\n return Buffer.from(ab);\n }\n}\n"],"mappings":"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","names":["import_types"]}
|
|
1
|
+
{"version":3,"file":"addon.js","sources":["../src/rtsp-camera.ts","../src/addon.ts"],"sourcesContent":["import { z } from \"zod\";\nimport { BaseDevice, DeviceType, DeviceFeature, classifyStreams, hydrateSchema, snapshotCapability, maskUrlCredentials } from \"@camstack/types\";\nimport type {\n ConfigUISchema,\n ConfigUISchemaWithValues,\n DeviceContext,\n ICameraDevice,\n InferNativeProvider,\n StreamMetadata,\n StreamQuality,\n StreamSourceEntry,\n} from \"@camstack/types\";\n\n/**\n * Stable, provider-assigned ids within a (deviceId) scope.\n *\n * `stream-1/2/3` map 1:1 with the operator-visible input slots in the\n * creation form. Once assigned at create time (or via legacy migration)\n * the id is permanent — editing a URL keeps its slot. Separating the id\n * from the quality lets the stream-broker decide the (high/mid/low)\n * mapping from probed metadata without the driver having to re-label.\n */\nconst streamEntrySchema = z.object({\n id: z.string().regex(/^stream-\\d+$/).describe(\"Stable slot id (stream-1/2/3)\"),\n url: z.string().describe(\"RTSP stream URL\"),\n profileHint: z.enum(['high', 'mid', 'low']).optional().describe(\"Provider-suggested profile — advisory only\"),\n resolution: z\n .object({ width: z.number().int().positive(), height: z.number().int().positive() })\n .optional()\n .describe(\"Probed resolution, if known\"),\n});\n\nexport type RtspStreamEntry = z.infer<typeof streamEntrySchema>\n\n/**\n * Accepts both the new `{id, url, profileHint?, resolution?}` shape AND\n * the legacy `{label, url}` shape. Legacy entries get rewritten to the\n * new shape at parse time so the rest of the driver can treat storage\n * uniformly. The constructor then persists the new shape back to disk\n * in `migrateLegacyStreamsIfNeeded` so subsequent parses are already\n * on the new shape.\n */\nconst streamsField = z.preprocess(\n (raw: unknown): unknown => {\n if (!Array.isArray(raw)) return raw\n return (raw as readonly unknown[]).map((entry, i): unknown => {\n if (!entry || typeof entry !== 'object') return entry\n const o = entry as Record<string, unknown>\n if (typeof o['id'] === 'string') return entry\n if (typeof o['label'] === 'string' && typeof o['url'] === 'string') {\n return {\n id: `stream-${i + 1}`,\n url: o['url'],\n profileHint: o['label'],\n }\n }\n return entry\n })\n },\n z.array(streamEntrySchema).min(1),\n)\n\nexport const rtspCameraSchema = z.object({\n // `name` is now base meta (DB column on `device-meta`, not in this\n // hardware-config schema). Read via `this.name` (resolved by\n // `BaseDevice` from `ctx.deviceMeta.name`); mutated via\n // `kernel.devices.setName(id, name)`.\n streams: streamsField.describe(\"Published RTSP streams\"),\n snapshotUrl: z.string().default(\"\").describe(\"Snapshot URL (optional)\"),\n username: z.string().default(\"\").describe(\"Username\"),\n password: z.string().default(\"\").describe(\"Password\"),\n});\n\n/**\n * Legacy storage shape: `[{ label: 'high'|'mid'|'low', url }]`.\n * Migrated to the new `[{id, url, profileHint}]` shape on construct\n * via `config.setAll`. The conversion is permanent — after one boot\n * every camera row is on the new shape.\n */\nconst legacyStreamSchema = z.object({\n label: z.enum(['high', 'mid', 'low']),\n url: z.string(),\n})\n\nexport class RtspCamera\n extends BaseDevice<typeof rtspCameraSchema>\n implements ICameraDevice\n{\n readonly type = DeviceType.Camera as const;\n features = [] as const satisfies readonly DeviceFeature[];\n\n constructor(ctx: DeviceContext) {\n super(\n ctx,\n rtspCameraSchema,\n { type: DeviceType.Camera },\n );\n // Online state is now firmware/broker-event driven only — BaseDevice\n // seeds `device-status` slice with online=false, and the provider's\n // stream-health aggregator (`BaseDeviceProvider.subscribeStreamHealth`)\n // flips it true on the first `stream.online` event.\n this.migrateLegacyStreamsIfNeeded(ctx.persistedConfig ?? {})\n this.registerSnapshotProvider();\n }\n\n /**\n * Detect legacy `{label, url}` stream shapes and reshape to\n * `{id, url, profileHint}`. Persists via `config.setAll` once, so\n * subsequent boots find the new shape and this branch is inert.\n *\n * Runs fire-and-forget — the constructor can't await. The reshape is\n * additive (ids are newly minted) so a failed persist is recoverable\n * on next boot.\n */\n private migrateLegacyStreamsIfNeeded(initialData: Record<string, unknown>): void {\n const raw = initialData['streams']\n if (!Array.isArray(raw)) return\n // All entries already have `id` → new shape; nothing to do.\n if (raw.every((s) => s && typeof s === 'object' && typeof (s as Record<string, unknown>)['id'] === 'string')) {\n return\n }\n const migrated: RtspStreamEntry[] = []\n let nextIdx = 1\n for (const entry of raw) {\n const parsed = legacyStreamSchema.safeParse(entry)\n if (!parsed.success) continue\n migrated.push({\n id: `stream-${nextIdx++}`,\n url: parsed.data.url,\n profileHint: parsed.data.label,\n })\n }\n if (migrated.length === 0) return\n const snapshotUrl = this.config.get('snapshotUrl') ?? ''\n const username = this.config.get('username') ?? ''\n const password = this.config.get('password') ?? ''\n this.ctx.logger.info('Migrating legacy {label,url} streams to {id,url,profileHint}', {\n tags: { stableId: this.stableId },\n meta: { streamCount: migrated.length },\n })\n void this.config\n .setAll({ streams: migrated, snapshotUrl, username, password })\n .catch((err: unknown) => {\n this.ctx.logger.warn('Legacy stream migration failed', {\n tags: { stableId: this.stableId },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n })\n }\n\n /**\n * Publish every configured stream to the system stream-broker as a\n * `pull-rtsp` cam stream. Idempotent — the broker's publishCameraStream\n * entry is keyed by `(deviceId, camStreamId)` so callers may invoke\n * this at will (e.g. on stream-broker restart). Fire-and-forget safe.\n */\n /**\n * Lifecycle hook fired by the kernel after registration. Publishes\n * the camera's streams to the broker — the kernel doesn't know\n * about the broker, but the camera does. Best-effort: if the\n * broker isn't ready, the provider's `system.ready-state`\n * subscription re-publishes on the next ready fire.\n */\n override async onActivate(): Promise<void> {\n await this.publishToBroker().catch((err: unknown) => {\n this.ctx.logger.warn('publishToBroker on activate failed — will retry on broker ready', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n })\n }\n\n async publishToBroker(): Promise<void> {\n const streams = this.config.get('streams')\n for (const [i, s] of streams.entries()) {\n try {\n await this.ctx.api.streamBroker.publishCameraStream.mutate({\n deviceId: this.id,\n camStreamId: s.id,\n kind: 'pull-rtsp',\n url: s.url,\n ...(s.resolution ? { resolution: s.resolution } : {}),\n label: `Stream ${i + 1}`,\n })\n } catch (err: unknown) {\n this.ctx.logger.warn('publishCameraStream failed', {\n tags: { deviceId: this.id, camStreamId: s.id },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n }\n }\n }\n\n /**\n * Thin compat shim: reflects the stored `{id, url, profileHint?}`\n * streams into the legacy `StreamSourceEntry` shape so\n * `ICameraDevice`-aware consumers (device-manager legacy paths,\n * device-cap-proxy fallback) still find data. The broker itself does\n * NOT read this anymore — it consumes `publishCameraStream`. This\n * shim goes away when every consumer migrates (C5/C7).\n */\n async getStreamSources(): Promise<readonly StreamSourceEntry[]> {\n const streams = this.config.get('streams')\n return streams.map((s): StreamSourceEntry => ({\n id: s.id,\n label: s.profileHint ?? s.id,\n protocol: 'rtsp' as const,\n url: s.url,\n ...(s.profileHint ? { profileHint: s.profileHint } : {}),\n ...(s.resolution ? { resolution: s.resolution } : {}),\n }))\n }\n\n override async removeDevice(): Promise<void> {\n this.ctx.logger.info('Removing RTSP camera', { meta: { stableId: this.stableId } });\n const streams = this.config.get('streams')\n await Promise.all(\n streams.map((s) =>\n this.ctx.api.streamBroker.retractCameraStream\n .mutate({ deviceId: this.id, camStreamId: s.id })\n .catch((err: unknown) => {\n this.ctx.logger.warn('retractCameraStream failed', {\n tags: { deviceId: this.id, camStreamId: s.id },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n }),\n ),\n )\n }\n\n // ── Driver-authored settings UI ────────────────────────────────────────\n //\n // Streams are stored as `Array<{id, url, profileHint?, resolution?}>`\n // but the settings UI exposes them as a `multiple` probe field of raw\n // URLs — the form only cares about the ordered list of URLs, the ids\n // and classification live in storage. Values are projected from\n // storage into the flat form keys referenced by the schema.\n\n override getSettingsUISchema(): ConfigUISchemaWithValues {\n const schema: ConfigUISchema = {\n sections: [\n // The `name` field is now base meta — rendered by the\n // device-manager's own settings contribution (with the\n // location field), NOT by the driver's hardware-config\n // schema. Driver UIs only carry technical knobs.\n {\n id: 'streams',\n title: 'RTSP Streams',\n description: 'Provide 1–3 RTSP URLs for this camera. After save, each URL is probed and the stream-broker assigns the (high / mid / low) profiles by resolution (highest pixel count → high). Input slot order in this form does not affect the broker profile mapping.',\n columns: 1,\n fields: [\n {\n type: 'probe',\n key: 'streams',\n label: 'RTSP stream URL',\n required: true,\n placeholder: 'rtsp://user:pass@host:554/Streaming/Channels/101',\n inputType: 'url',\n multiple: {\n min: 1,\n max: 3,\n addLabel: 'Add stream',\n itemLabel: 'Stream ${n}',\n itemDefault: '',\n },\n },\n ],\n },\n {\n id: 'snapshot',\n title: 'Snapshot',\n columns: 1,\n fields: [\n { type: 'probe', key: 'snapshotUrl', label: 'Snapshot URL', description: 'Optional HTTP(S) endpoint that returns a JPEG still.', placeholder: 'http://host/ISAPI/Streaming/channels/101/picture', inputType: 'url' },\n ],\n },\n {\n id: 'credentials',\n title: 'Credentials',\n description: 'Optional. If the RTSP URL already contains user:pass@ you can leave these empty.',\n columns: 2,\n fields: [\n { type: 'text', key: 'username', label: 'Username' },\n { type: 'password', key: 'password', label: 'Password', showToggle: true },\n ],\n },\n ],\n }\n return hydrateSchema(schema, this.collectFormValues())\n }\n\n /**\n * Project stored config into the flat UI keys referenced by the schema.\n * Form only sees URLs — the internal id/profileHint/resolution stay\n * invisible to the operator (the broker decides profiles from probed\n * metadata at save time).\n */\n private collectFormValues(): Record<string, unknown> {\n const stored = this.config.get('streams')\n // Preserve the slot order from storage (`stream-1`, `stream-2`, …).\n const sorted = [...stored].sort((a, b) => a.id.localeCompare(b.id, 'en', { numeric: true }))\n const urls = sorted.map((s) => s.url).filter((u) => u.length > 0)\n return {\n streams: urls,\n snapshotUrl: this.config.get('snapshotUrl'),\n username: this.config.get('username'),\n password: this.config.get('password'),\n }\n }\n\n override async applySettingsPatch(patch: Record<string, unknown>): Promise<void> {\n const current = this.collectFormValues()\n const merged: Record<string, unknown> = { ...current, ...patch }\n const pickStr = (k: string): string => (typeof merged[k] === 'string' ? (merged[k] as string).trim() : '')\n\n const next: Record<string, unknown> = {}\n\n const rawStreams = merged['streams']\n const incomingUrls: string[] = Array.isArray(rawStreams)\n ? rawStreams\n .filter((s): s is string => typeof s === 'string')\n .map((s) => s.trim())\n .filter((s) => s.length > 0)\n : []\n\n if (incomingUrls.length === 0) {\n // UI cleared every slot — keep current streams so zod's min(1)\n // invariant holds and the camera keeps streaming until the next\n // save with real URLs.\n next.streams = this.config.get('streams')\n } else {\n const probed = await this.probeAndClassify(incomingUrls.slice(0, 3))\n next.streams = probed\n }\n\n next.snapshotUrl = pickStr('snapshotUrl')\n next.username = pickStr('username')\n next.password = typeof merged['password'] === 'string' ? merged['password'] : this.config.get('password')\n\n await this.config.setAll(next)\n\n // Storage now has the new stream list — re-publish so the broker\n // sees any URL/resolution changes. `publishCameraStream` is\n // idempotent for the same camStreamId so slots that didn't change\n // are free no-ops.\n await this.publishToBroker()\n }\n\n /**\n * Probe a list of RTSP URLs through the kernel stream-probe and return\n * the new `{id, url, profileHint?, resolution?}` entries. Ids are\n * assigned `stream-1/2/3` in input order so the UI's slot ordering is\n * preserved across edits. The broker is in charge of the actual\n * (high/mid/low) profile assignment via `computeInitialAssignment`.\n */\n private async probeAndClassify(urls: readonly string[]): Promise<RtspStreamEntry[]> {\n const streamProbe = this.ctx.streamProbe\n if (!streamProbe) {\n this.ctx.logger.warn(\n '[rtsp-edit] ctx.streamProbe unavailable — falling back to input-order profile assignment',\n { meta: { streamCount: urls.length } },\n )\n const sequence: ReadonlyArray<StreamQuality> =\n urls.length === 1 ? ['high']\n : urls.length === 2 ? ['high', 'low']\n : ['high', 'mid', 'low']\n return urls.map((url, i): RtspStreamEntry => ({\n id: `stream-${i + 1}`,\n url,\n profileHint: sequence[i]!,\n }))\n }\n\n const probed = await Promise.all(\n urls.map(async (url) => {\n try {\n const metadata = await streamProbe.probe(url, { force: false })\n return { url, metadata }\n } catch (err) {\n this.ctx.logger.warn(\n '[rtsp-edit] probe failed — landing in the lowest tier via classifyStreams',\n { meta: { url: maskUrlCredentials(url), error: err instanceof Error ? err.message : String(err) } },\n )\n const emptyMetadata: StreamMetadata = {}\n return { url, metadata: emptyMetadata }\n }\n }),\n )\n const classified = classifyStreams(probed)\n // Each classified entry carries a quality tag. We preserve the\n // input order for the slot id and use the tag as `profileHint`;\n // resolution comes from the probed metadata (matched by url).\n const metadataByUrl = new Map(probed.map((p) => [p.url, p.metadata]))\n const classifiedByUrl = new Map(classified.map((c) => [c.url, c]))\n return urls.map((url, i): RtspStreamEntry => {\n const tag = classifiedByUrl.get(url)\n const meta = metadataByUrl.get(url)\n const entry: RtspStreamEntry = { id: `stream-${i + 1}`, url }\n if (tag) entry.profileHint = tag.quality\n if (meta?.width && meta?.height) entry.resolution = { width: meta.width, height: meta.height }\n return entry\n })\n }\n\n // ── Native capabilities ────────────────────────────────────────────────\n\n private registerSnapshotProvider(): void {\n const snapshotUrl = (this.config.get(\"snapshotUrl\") ?? \"\").trim();\n if (!snapshotUrl) return;\n\n const provider: InferNativeProvider<typeof snapshotCapability> = {\n getSnapshot: async ({ deviceId }) => {\n if (deviceId !== this.id) {\n throw new Error(`RtspCamera: deviceId mismatch, expected ${this.id}, got ${deviceId}`);\n }\n const buf = await this.fetchHttpSnapshot(snapshotUrl);\n if (buf.length === 0) return null;\n return { base64: buf.toString(\"base64\"), contentType: \"image/jpeg\" };\n },\n invalidateCache: async () => {\n /* no-op — caching is the snapshot orchestrator's concern */\n },\n };\n this.ctx.registerNativeCap(snapshotCapability, provider);\n }\n\n private async fetchHttpSnapshot(url: string): Promise<Buffer> {\n const username = (this.config.get(\"username\") ?? \"\").trim();\n const password = (this.config.get(\"password\") ?? \"\").trim();\n const headers: Record<string, string> = {};\n if (username || password) {\n const creds = Buffer.from(`${username}:${password}`).toString(\"base64\");\n headers[\"authorization\"] = `Basic ${creds}`;\n }\n const res = await fetch(url, { headers });\n if (!res.ok) throw new Error(`HTTP ${res.status} ${res.statusText}`);\n const ab = await res.arrayBuffer();\n return Buffer.from(ab);\n }\n}\n","import type {\n ConfigUISchema,\n CreateDeviceSpec,\n DeviceConstructor,\n FieldProbeResult,\n IDevice,\n ProviderRegistration,\n StreamQuality,\n SystemEvent,\n} from '@camstack/types'\nimport { BaseDeviceProvider, DeviceType, classifyStreams, EventCategory, maskUrlCredentials } from '@camstack/types'\nimport { RtspCamera, rtspCameraSchema, type RtspStreamEntry } from './rtsp-camera.js'\n\nfunction getString(obj: Record<string, unknown>, key: string): string {\n const v = obj[key]\n return typeof v === 'string' ? v : ''\n}\n\nfunction getStringArray(obj: Record<string, unknown>, key: string): readonly string[] {\n const v = obj[key]\n if (!Array.isArray(v)) return []\n return v\n .filter((s): s is string => typeof s === 'string')\n .map((s) => s.trim())\n .filter((s) => s.length > 0)\n}\n\n/**\n * Build the creation form schema. Hand-written ConfigUISchema instead of\n * converting from Zod — this lets us expose probe-typed fields (live\n * ffprobe \"Test\" button) that the automatic converter doesn't emit.\n *\n * `streams` is a single probe field rendered as a `multiple` array —\n * 1..3 instances; the backend probes each via the kernel's\n * `StreamProbeService` and publishes them to the stream-broker, which\n * decides the (high / mid / low) profile mapping by resolution.\n */\nfunction buildCreationFormSchema(): ConfigUISchema {\n return {\n sections: [\n {\n id: 'identity',\n title: 'Camera',\n columns: 1,\n fields: [\n { type: 'text', key: 'name', label: 'Name', required: true, placeholder: 'Living room' },\n ],\n },\n {\n id: 'streams',\n title: 'RTSP Streams',\n description: 'Provide 1–3 RTSP URLs for this camera. Each URL is probed and published to the stream-broker; the broker assigns the (high / mid / low) profiles by resolution — you do not need to label the streams manually.',\n columns: 1,\n fields: [\n {\n type: 'probe',\n key: 'streams',\n label: 'RTSP stream URL',\n required: true,\n placeholder: 'rtsp://user:pass@host:554/Streaming/Channels/101',\n inputType: 'url',\n multiple: {\n min: 1,\n max: 3,\n addLabel: 'Add stream',\n itemLabel: 'Stream ${n}',\n itemDefault: '',\n },\n },\n ],\n },\n {\n id: 'snapshot',\n title: 'Snapshot',\n columns: 1,\n fields: [\n { type: 'probe', key: 'snapshotUrl', label: 'Snapshot URL', description: 'Optional HTTP(S) endpoint that returns a JPEG still.', placeholder: 'http://host/ISAPI/Streaming/channels/101/picture', inputType: 'url' },\n ],\n },\n {\n id: 'credentials',\n title: 'Credentials',\n description: 'Optional. If the RTSP URL already contains user:pass@ you can leave these empty.',\n columns: 2,\n fields: [\n { type: 'text', key: 'username', label: 'Username' },\n { type: 'password', key: 'password', label: 'Password', showToggle: true },\n ],\n },\n ],\n }\n}\n\n/**\n * Re-publish every RtspCamera's streams to the broker whenever the\n * broker emits `system.ready-state` state=ready. Handles boot-order\n * races (broker starts after provider) and broker restart recovery.\n * At normal runtime, `onCreateDevice` publishes eagerly so new cameras\n * don't have to wait for the next ready fire.\n */\nfunction isStreamBrokerReady(event: SystemEvent): boolean {\n if (event.category !== 'system.ready-state') return false\n const data = event.data as Record<string, unknown>\n return data['capName'] === 'stream-broker' && data['state'] === 'ready'\n}\n\nexport class RtspProviderAddon extends BaseDeviceProvider {\n protected readonly addonId = 'provider-rtsp'\n protected readonly providerName = 'RTSP'\n protected readonly deviceClasses: Partial<Record<DeviceType, DeviceConstructor<IDevice>>> = {\n [DeviceType.Camera]: RtspCamera,\n }\n\n constructor() { super({}) }\n\n protected override async onInitialize(): Promise<ProviderRegistration[]> {\n const regs = await super.onInitialize()\n // Autonomous online tracking — mirror `state.cameraStreams.online`\n // (written by the stream-broker on every health flip) into\n // `device.online` for every RTSP-owned device. RTSP has no firmware\n // liveness signal, so the broker's view is the canonical source.\n // Reolink, which has its own firmware push events, doesn't subscribe.\n this.subscribe(\n { category: EventCategory.DeviceStateChanged },\n (event) => {\n const data = event.data as { deviceId?: number; capName?: string; slice?: { online?: boolean } }\n if (data.capName !== 'camera-streams') return\n const deviceId = data.deviceId\n if (typeof deviceId !== 'number') return\n const registry = this.ctx.kernel.deviceRegistry\n if (!registry) return\n if (registry.getAddonId(deviceId) !== this.addonId) return\n const device = registry.getById(deviceId)\n if (!device) return\n const online = data.slice?.online === true\n if (device.online !== online) device.online = online\n },\n )\n this.subscribe(\n { category: EventCategory.SystemReadyState },\n (event) => {\n if (!isStreamBrokerReady(event)) return\n void this.republishAll().catch((err: unknown) => {\n this.ctx.logger.warn('Failed to re-publish RTSP streams after broker ready', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n })\n },\n )\n return regs\n }\n\n // ── Creation ─────────────────────────────────────────────────────────\n\n protected async onGetCreationSchema(type: DeviceType): Promise<ConfigUISchema | null> {\n if (type !== DeviceType.Camera) return null\n return buildCreationFormSchema()\n }\n\n protected async onCreateDevice(type: DeviceType, config: Record<string, unknown>): Promise<CreateDeviceSpec> {\n if (type !== DeviceType.Camera) {\n throw new Error(`RTSP provider does not support device type: ${type}`)\n }\n\n const name = getString(config, 'name').trim()\n if (!name) throw new Error('Camera name is required')\n\n const rawStreams = getStringArray(config, 'streams')\n if (rawStreams.length === 0) throw new Error('At least one RTSP stream URL is required')\n if (rawStreams.length > 3) throw new Error('At most 3 RTSP stream URLs are supported')\n\n const streamProbe = this.ctx.kernel.streamProbe\n if (!streamProbe) throw new Error('ctx.kernel.streamProbe unavailable — cannot probe streams')\n const probed = await Promise.all(\n rawStreams.map(async (url) => {\n try {\n const metadata = await streamProbe.probe(url, { force: false })\n return { url, metadata, ok: true as const }\n } catch (err) {\n this.ctx.logger.warn(\n '[rtsp-create] probe failed — publishing without probed metadata',\n { meta: { url: maskUrlCredentials(url), error: err instanceof Error ? err.message : String(err) } },\n )\n return { url, metadata: undefined, ok: false as const }\n }\n }),\n )\n\n const classified = classifyStreams(\n probed.map((p) => ({ url: p.url, metadata: p.metadata ?? {} })),\n )\n const hintByUrl = new Map<string, StreamQuality>(classified.map((c) => [c.url, c.quality]))\n\n const streams: RtspStreamEntry[] = probed.map((p, i): RtspStreamEntry => {\n const entry: RtspStreamEntry = { id: `stream-${i + 1}`, url: p.url }\n const hint = hintByUrl.get(p.url)\n if (hint) entry.profileHint = hint\n if (p.metadata?.width && p.metadata?.height) {\n entry.resolution = { width: p.metadata.width, height: p.metadata.height }\n }\n return entry\n })\n\n const parsed = rtspCameraSchema.parse({\n streams,\n snapshotUrl: getString(config, 'snapshotUrl'),\n username: getString(config, 'username'),\n password: getString(config, 'password'),\n })\n\n return {\n meta: { type: DeviceType.Camera, name },\n config: parsed,\n }\n }\n\n // ── Field probing ────────────────────────────────────────────────────\n\n override async testCreationField(input: {\n type: DeviceType\n key: string\n value: unknown\n }): Promise<FieldProbeResult> {\n if (input.type !== DeviceType.Camera) {\n return { status: 'error', error: `Unsupported device type: ${input.type}` }\n }\n const streamProbe = this.ctx.kernel.streamProbe\n if (!streamProbe) {\n return { status: 'error', error: 'ctx.kernel.streamProbe unavailable — cannot reach kernel probe' }\n }\n return streamProbe.probeField(input.key, input.value)\n }\n\n // ── Restore ──────────────────────────────────────────────────────────\n //\n // `BaseDeviceProvider.onRestoreDevices` default impl iterates\n // `savedDevices`, looks up the right class via `deviceClasses`,\n // and calls `kernel.devices.create()`. Each created device fires\n // its `onCreated` lifecycle hook (see `RtspCamera.onCreated()`)\n // which publishes streams to the broker — no per-provider override\n // needed here.\n\n // ── Internal — re-publish every RtspCamera to the broker ────────────\n\n private async republishAll(): Promise<void> {\n const all = (await this.ctx.kernel.devices?.getAll()) ?? []\n let published = 0\n for (const dev of all as readonly IDevice[]) {\n if (!(dev instanceof RtspCamera)) continue\n try {\n await dev.publishToBroker()\n published++\n } catch (err) {\n this.ctx.logger.debug('publishToBroker threw during republish', {\n tags: { deviceId: dev.id },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n }\n }\n if (published > 0) {\n this.ctx.logger.info('Re-published RTSP streams to stream-broker', {\n meta: { published, total: all.length },\n })\n }\n }\n}\n"],"names":["z","BaseDevice","DeviceType","hydrateSchema","maskUrlCredentials","classifyStreams","snapshotCapability","BaseDeviceProvider","EventCategory"],"mappings":"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"}
|