@camstack/types 1.2.43 → 1.2.44
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 +8 -2
- package/dist/addon.mjs +8 -2
- package/dist/capabilities/index.d.ts +5 -2
- package/dist/capabilities/osd-manager.cap.d.ts +900 -0
- package/dist/capabilities/pipeline-analytics.cap.d.ts +171 -4
- package/dist/capabilities/pipeline-orchestrator.cap.d.ts +10 -0
- package/dist/capabilities/schemas/streaming-shared.d.ts +2 -0
- package/dist/capabilities/server-management.cap.d.ts +3 -3
- package/dist/capabilities/stream-broker.cap.d.ts +32 -0
- package/dist/ffmpeg/fmp4-box-splitter.d.ts +113 -0
- package/dist/ffmpeg/fmp4-fragment-child.d.ts +85 -0
- package/dist/ffmpeg/fmp4-fragment-plane.d.ts +142 -0
- package/dist/ffmpeg/invocation.d.ts +4 -2
- package/dist/fmp4-box-splitter-B53u9-Nu.mjs +615 -0
- package/dist/fmp4-box-splitter-BkWH7O3L.js +686 -0
- package/dist/generated/addon-api.d.ts +85 -0
- package/dist/generated/cap-input-defaults.d.ts +1 -1
- package/dist/generated/capability-router-map.d.ts +5 -2
- package/dist/generated/device-proxy.d.ts +3 -1
- package/dist/generated/method-access-map.d.ts +1 -1
- package/dist/generated/system-proxy.d.ts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +581 -404
- package/dist/index.mjs +558 -394
- package/dist/interfaces/camera-switches.d.ts +48 -0
- package/dist/interfaces/stream-broker.d.ts +18 -0
- package/dist/node.d.ts +4 -0
- package/dist/node.js +509 -3
- package/dist/node.mjs +507 -3
- package/dist/notification/schedule.d.ts +20 -0
- package/dist/{sleep-DtstvzWm.mjs → sleep-cC4Fuup8.mjs} +26 -1
- package/dist/{sleep-Bx9IIoT0.js → sleep-eiC10_cX.js} +26 -1
- package/dist/types/pipeline-step.d.ts +1 -1
- package/package.json +1 -1
- package/dist/canonical-hash-7nfBbEqR.mjs +0 -35
- package/dist/canonical-hash-BcZHRHIx.js +0 -40
|
@@ -247,6 +247,31 @@ export declare const TrackSourceSchema: z.ZodEnum<{
|
|
|
247
247
|
pipeline: "pipeline";
|
|
248
248
|
}>;
|
|
249
249
|
export type TrackSource = z.infer<typeof TrackSourceSchema>;
|
|
250
|
+
/**
|
|
251
|
+
* Where a track sits in the RETRAIN lifecycle (D81).
|
|
252
|
+
*
|
|
253
|
+
* - `none` — never marked, or un-marked. Evictable.
|
|
254
|
+
* - `staging` — the operator wants this track as training material and has not
|
|
255
|
+
* finished with it. **This is the only state retention holds**: the track and
|
|
256
|
+
* everything it owns (object events, crops, keyframes, CLIP vector) survive
|
|
257
|
+
* the device's age window.
|
|
258
|
+
* - `trained` — the retrain page has taken what it needed. The frames it chose
|
|
259
|
+
* were COPIED into the retrain dataset at selection time, so the dataset no
|
|
260
|
+
* longer depends on the track's media and the track becomes EVICTABLE again.
|
|
261
|
+
* Terminal for the plain `markForTrain` toggle: returning it to `staging` is
|
|
262
|
+
* a deliberate action of the retrain page, not a side effect of a checkbox.
|
|
263
|
+
*
|
|
264
|
+
* There is no `null`. The state is stored `TEXT NOT NULL DEFAULT 'none'` because
|
|
265
|
+
* the store's filter language has only positive equality and `whereIn` — no
|
|
266
|
+
* negation, no IS NULL — so a NULL would be unselectable by ANY predicate and
|
|
267
|
+
* would make the entire pre-column history immortal in one deploy.
|
|
268
|
+
*/
|
|
269
|
+
export declare const RetrainStatusSchema: z.ZodEnum<{
|
|
270
|
+
none: "none";
|
|
271
|
+
staging: "staging";
|
|
272
|
+
trained: "trained";
|
|
273
|
+
}>;
|
|
274
|
+
export type RetrainStatus = z.infer<typeof RetrainStatusSchema>;
|
|
250
275
|
/**
|
|
251
276
|
* The write half: a PARTIAL patch. An omitted key is left untouched, so setting
|
|
252
277
|
* one flag can never clear the other — the toggles are independent and are
|
|
@@ -266,9 +291,45 @@ export declare const TrackFlagsSchema: z.ZodObject<{
|
|
|
266
291
|
trackId: z.ZodString;
|
|
267
292
|
markForTrain: z.ZodBoolean;
|
|
268
293
|
debug: z.ZodBoolean;
|
|
294
|
+
retrainStatus: z.ZodEnum<{
|
|
295
|
+
none: "none";
|
|
296
|
+
staging: "staging";
|
|
297
|
+
trained: "trained";
|
|
298
|
+
}>;
|
|
269
299
|
}, z.core.$strip>;
|
|
270
300
|
export type TrackFlags = z.infer<typeof TrackFlagsSchema>;
|
|
301
|
+
/** Per-camera slice of a training-export estimate. */
|
|
302
|
+
export declare const TrainingExportDeviceTotalsSchema: z.ZodObject<{
|
|
303
|
+
deviceId: z.ZodNumber;
|
|
304
|
+
tracks: z.ZodNumber;
|
|
305
|
+
files: z.ZodNumber;
|
|
306
|
+
bytes: z.ZodNumber;
|
|
307
|
+
}, z.core.$strip>;
|
|
308
|
+
export type TrainingExportDeviceTotals = z.infer<typeof TrainingExportDeviceTotalsSchema>;
|
|
309
|
+
/**
|
|
310
|
+
* What a training export WOULD contain. Computed from media index rows only —
|
|
311
|
+
* no blob is read to produce this.
|
|
312
|
+
*/
|
|
313
|
+
export declare const TrainingExportSummarySchema: z.ZodObject<{
|
|
314
|
+
generatedAt: z.ZodNumber;
|
|
315
|
+
trackCount: z.ZodNumber;
|
|
316
|
+
fileCount: z.ZodNumber;
|
|
317
|
+
byteCount: z.ZodNumber;
|
|
318
|
+
truncated: z.ZodBoolean;
|
|
319
|
+
devices: z.ZodReadonly<z.ZodArray<z.ZodObject<{
|
|
320
|
+
deviceId: z.ZodNumber;
|
|
321
|
+
tracks: z.ZodNumber;
|
|
322
|
+
files: z.ZodNumber;
|
|
323
|
+
bytes: z.ZodNumber;
|
|
324
|
+
}, z.core.$strip>>>;
|
|
325
|
+
}, z.core.$strip>;
|
|
326
|
+
export type TrainingExportSummary = z.infer<typeof TrainingExportSummarySchema>;
|
|
271
327
|
declare const TrackSchema: z.ZodObject<{
|
|
328
|
+
retrainStatus: z.ZodOptional<z.ZodEnum<{
|
|
329
|
+
none: "none";
|
|
330
|
+
staging: "staging";
|
|
331
|
+
trained: "trained";
|
|
332
|
+
}>>;
|
|
272
333
|
markForTrain: z.ZodOptional<z.ZodBoolean>;
|
|
273
334
|
debug: z.ZodOptional<z.ZodBoolean>;
|
|
274
335
|
trackId: z.ZodString;
|
|
@@ -550,6 +611,11 @@ declare const RecentTracksQueryInput: z.ZodObject<{
|
|
|
550
611
|
export type RecentTracksQuery = z.infer<typeof RecentTracksQueryInput>;
|
|
551
612
|
declare const RecentTracksPageSchema: z.ZodObject<{
|
|
552
613
|
tracks: z.ZodReadonly<z.ZodArray<z.ZodObject<{
|
|
614
|
+
retrainStatus: z.ZodOptional<z.ZodEnum<{
|
|
615
|
+
none: "none";
|
|
616
|
+
staging: "staging";
|
|
617
|
+
trained: "trained";
|
|
618
|
+
}>>;
|
|
553
619
|
markForTrain: z.ZodOptional<z.ZodBoolean>;
|
|
554
620
|
debug: z.ZodOptional<z.ZodBoolean>;
|
|
555
621
|
trackId: z.ZodString;
|
|
@@ -622,6 +688,11 @@ declare const RecentTracksPageSchema: z.ZodObject<{
|
|
|
622
688
|
}, z.core.$strip>;
|
|
623
689
|
export type RecentTracksPage = z.infer<typeof RecentTracksPageSchema>;
|
|
624
690
|
declare const KeyEventSchema: z.ZodObject<{
|
|
691
|
+
retrainStatus: z.ZodOptional<z.ZodEnum<{
|
|
692
|
+
none: "none";
|
|
693
|
+
staging: "staging";
|
|
694
|
+
trained: "trained";
|
|
695
|
+
}>>;
|
|
625
696
|
markForTrain: z.ZodOptional<z.ZodBoolean>;
|
|
626
697
|
debug: z.ZodOptional<z.ZodBoolean>;
|
|
627
698
|
id: z.ZodString;
|
|
@@ -744,6 +815,11 @@ export declare const pipelineAnalyticsCapability: {
|
|
|
744
815
|
readonly getActiveTracks: import("./capability-definition.js").CapabilityMethodSchema<z.ZodObject<{
|
|
745
816
|
deviceId: z.ZodNumber;
|
|
746
817
|
}, z.core.$strip>, z.ZodReadonly<z.ZodArray<z.ZodObject<{
|
|
818
|
+
retrainStatus: z.ZodOptional<z.ZodEnum<{
|
|
819
|
+
none: "none";
|
|
820
|
+
staging: "staging";
|
|
821
|
+
trained: "trained";
|
|
822
|
+
}>>;
|
|
747
823
|
markForTrain: z.ZodOptional<z.ZodBoolean>;
|
|
748
824
|
debug: z.ZodOptional<z.ZodBoolean>;
|
|
749
825
|
trackId: z.ZodString;
|
|
@@ -816,6 +892,11 @@ export declare const pipelineAnalyticsCapability: {
|
|
|
816
892
|
deviceId: z.ZodNumber;
|
|
817
893
|
trackId: z.ZodString;
|
|
818
894
|
}, z.core.$strip>, z.ZodNullable<z.ZodObject<{
|
|
895
|
+
retrainStatus: z.ZodOptional<z.ZodEnum<{
|
|
896
|
+
none: "none";
|
|
897
|
+
staging: "staging";
|
|
898
|
+
trained: "trained";
|
|
899
|
+
}>>;
|
|
819
900
|
markForTrain: z.ZodOptional<z.ZodBoolean>;
|
|
820
901
|
debug: z.ZodOptional<z.ZodBoolean>;
|
|
821
902
|
trackId: z.ZodString;
|
|
@@ -910,6 +991,11 @@ export declare const pipelineAnalyticsCapability: {
|
|
|
910
991
|
slim: "slim";
|
|
911
992
|
}>>;
|
|
912
993
|
}, z.core.$strip>, z.ZodReadonly<z.ZodArray<z.ZodObject<{
|
|
994
|
+
retrainStatus: z.ZodOptional<z.ZodEnum<{
|
|
995
|
+
none: "none";
|
|
996
|
+
staging: "staging";
|
|
997
|
+
trained: "trained";
|
|
998
|
+
}>>;
|
|
913
999
|
markForTrain: z.ZodOptional<z.ZodBoolean>;
|
|
914
1000
|
debug: z.ZodOptional<z.ZodBoolean>;
|
|
915
1001
|
trackId: z.ZodString;
|
|
@@ -1000,6 +1086,11 @@ export declare const pipelineAnalyticsCapability: {
|
|
|
1000
1086
|
}>>;
|
|
1001
1087
|
}, z.core.$strip>, z.ZodObject<{
|
|
1002
1088
|
tracks: z.ZodReadonly<z.ZodArray<z.ZodObject<{
|
|
1089
|
+
retrainStatus: z.ZodOptional<z.ZodEnum<{
|
|
1090
|
+
none: "none";
|
|
1091
|
+
staging: "staging";
|
|
1092
|
+
trained: "trained";
|
|
1093
|
+
}>>;
|
|
1003
1094
|
markForTrain: z.ZodOptional<z.ZodBoolean>;
|
|
1004
1095
|
debug: z.ZodOptional<z.ZodBoolean>;
|
|
1005
1096
|
trackId: z.ZodString;
|
|
@@ -1323,6 +1414,11 @@ export declare const pipelineAnalyticsCapability: {
|
|
|
1323
1414
|
minImportance: z.ZodOptional<z.ZodNumber>;
|
|
1324
1415
|
classFilter: z.ZodOptional<z.ZodString>;
|
|
1325
1416
|
}, z.core.$strip>, z.ZodReadonly<z.ZodArray<z.ZodObject<{
|
|
1417
|
+
retrainStatus: z.ZodOptional<z.ZodEnum<{
|
|
1418
|
+
none: "none";
|
|
1419
|
+
staging: "staging";
|
|
1420
|
+
trained: "trained";
|
|
1421
|
+
}>>;
|
|
1326
1422
|
markForTrain: z.ZodOptional<z.ZodBoolean>;
|
|
1327
1423
|
debug: z.ZodOptional<z.ZodBoolean>;
|
|
1328
1424
|
id: z.ZodString;
|
|
@@ -1434,11 +1530,29 @@ export declare const pipelineAnalyticsCapability: {
|
|
|
1434
1530
|
*
|
|
1435
1531
|
* `auth: 'protected'` (the default), NOT `admin`: the viewer is an
|
|
1436
1532
|
* authenticated non-admin surface and two of the three call sites are
|
|
1437
|
-
* there.
|
|
1438
|
-
*
|
|
1533
|
+
* there. The note that used to sit here said to revisit this the day a flag
|
|
1534
|
+
* gained an effect that costs storage, and D81 is that day — `markForTrain`
|
|
1535
|
+
* now pins. It STAYS protected, and the reason is that the alternative
|
|
1536
|
+
* makes the feature pointless: marking a track is something you do while
|
|
1537
|
+
* looking at it, on the surface you were already looking at it on, and that
|
|
1538
|
+
* surface is the viewer. What the storage cost gets instead is a BOUND — a
|
|
1539
|
+
* per-device pin budget enforced in the body, refusing a new pin past the
|
|
1540
|
+
* limit while always allowing un-marking. `deleteTracks` next door is still
|
|
1541
|
+
* admin, because destroying evidence and preserving it are not symmetric.
|
|
1439
1542
|
*
|
|
1440
|
-
*
|
|
1441
|
-
*
|
|
1543
|
+
* `markForTrain` writes the retrain LIFECYCLE, not a boolean column: `true`
|
|
1544
|
+
* is `none → staging`, `false` is `staging → none`. A track already
|
|
1545
|
+
* `trained` refuses BOTH — its frames are copies inside the retrain dataset
|
|
1546
|
+
* and re-staging it from a generic toggle is how the same material gets
|
|
1547
|
+
* annotated twice under two ground truths. Returning a trained track to
|
|
1548
|
+
* staging is a deliberate action of the retrain page, which is also the only
|
|
1549
|
+
* thing that produces `trained` in the first place.
|
|
1550
|
+
*
|
|
1551
|
+
* Returns the RESOLVED state of both flags (absent → `false`) plus the
|
|
1552
|
+
* `retrainStatus` they were derived from, so a caller can drive its toggle
|
|
1553
|
+
* — and render a `trained` badge — without a re-fetch. Rejects an unknown
|
|
1554
|
+
* track, a new staging mark on a device already holding its full budget, and
|
|
1555
|
+
* any `markForTrain` write against a trained track.
|
|
1442
1556
|
*/
|
|
1443
1557
|
readonly setTrackFlags: import("./capability-definition.js").CapabilityMethodSchema<z.ZodObject<{
|
|
1444
1558
|
deviceId: z.ZodNumber;
|
|
@@ -1451,6 +1565,11 @@ export declare const pipelineAnalyticsCapability: {
|
|
|
1451
1565
|
trackId: z.ZodString;
|
|
1452
1566
|
markForTrain: z.ZodBoolean;
|
|
1453
1567
|
debug: z.ZodBoolean;
|
|
1568
|
+
retrainStatus: z.ZodEnum<{
|
|
1569
|
+
none: "none";
|
|
1570
|
+
staging: "staging";
|
|
1571
|
+
trained: "trained";
|
|
1572
|
+
}>;
|
|
1454
1573
|
}, z.core.$strip>, "mutation">;
|
|
1455
1574
|
/**
|
|
1456
1575
|
* Durable event-store footprint for the management UI: event rows
|
|
@@ -1570,6 +1689,54 @@ export declare const pipelineAnalyticsCapability: {
|
|
|
1570
1689
|
detail: z.ZodNullable<z.ZodString>;
|
|
1571
1690
|
actor: z.ZodString;
|
|
1572
1691
|
}, z.core.$strip>>>, "query">;
|
|
1692
|
+
/**
|
|
1693
|
+
* The CHEAP QUESTION, asked before any media moves: how big is the dataset
|
|
1694
|
+
* the marked (`markForTrain`) tracks would produce?
|
|
1695
|
+
*
|
|
1696
|
+
* Answered from media INDEX rows only — key, kind, size, timestamp — so it
|
|
1697
|
+
* costs ~2 KB of reads per track and no blob reads at all. The measured harm
|
|
1698
|
+
* behind D56 was a bulk pass that read and base64'd every blob a track owned
|
|
1699
|
+
* before deciding anything, taking hub-main to 82 s busy out of 120; an
|
|
1700
|
+
* export is that same I/O shape, so it inherits the same discipline: know
|
|
1701
|
+
* the size, then decide.
|
|
1702
|
+
*
|
|
1703
|
+
* `truncated` reports that more marked tracks exist than one pass carries.
|
|
1704
|
+
* Empty `deviceIds` ⇒ every device that has marked tracks.
|
|
1705
|
+
*/
|
|
1706
|
+
readonly getTrainingExportSummary: import("./capability-definition.js").CapabilityMethodSchema<z.ZodObject<{
|
|
1707
|
+
deviceIds: z.ZodOptional<z.ZodArray<z.ZodNumber>>;
|
|
1708
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
1709
|
+
generatedAt: z.ZodNumber;
|
|
1710
|
+
trackCount: z.ZodNumber;
|
|
1711
|
+
fileCount: z.ZodNumber;
|
|
1712
|
+
byteCount: z.ZodNumber;
|
|
1713
|
+
truncated: z.ZodBoolean;
|
|
1714
|
+
devices: z.ZodReadonly<z.ZodArray<z.ZodObject<{
|
|
1715
|
+
deviceId: z.ZodNumber;
|
|
1716
|
+
tracks: z.ZodNumber;
|
|
1717
|
+
files: z.ZodNumber;
|
|
1718
|
+
bytes: z.ZodNumber;
|
|
1719
|
+
}, z.core.$strip>>>;
|
|
1720
|
+
}, z.core.$strip>, "query">;
|
|
1721
|
+
/**
|
|
1722
|
+
* Where to download the dataset archive.
|
|
1723
|
+
*
|
|
1724
|
+
* The BYTES do not come back through this cap — they come from the returned
|
|
1725
|
+
* data-plane URL, which streams a tar built entry by entry. A multi-gigabyte
|
|
1726
|
+
* archive base64'd through a unary RPC envelope would be held whole in
|
|
1727
|
+
* memory twice on a hub this repo has already OOM'd once (D9/D18 are the
|
|
1728
|
+
* same lesson about frames). `getDownloadUrl` on `recordingExport` is the
|
|
1729
|
+
* precedent, and this follows it deliberately.
|
|
1730
|
+
*
|
|
1731
|
+
* The archive contains a `manifest.json` FIRST, then the stored media
|
|
1732
|
+
* VERBATIM under `tracks/<deviceId>/<trackId>/…`. No crop is derived and no
|
|
1733
|
+
* model is run: a training set's pixels must be the pixels the pipeline saw.
|
|
1734
|
+
*/
|
|
1735
|
+
readonly getTrainingExportUrl: import("./capability-definition.js").CapabilityMethodSchema<z.ZodObject<{
|
|
1736
|
+
deviceIds: z.ZodOptional<z.ZodArray<z.ZodNumber>>;
|
|
1737
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
1738
|
+
url: z.ZodString;
|
|
1739
|
+
}, z.core.$strip>, "query">;
|
|
1573
1740
|
readonly getEventMedia: import("./capability-definition.js").CapabilityMethodSchema<z.ZodObject<{
|
|
1574
1741
|
eventId: z.ZodString;
|
|
1575
1742
|
kind: z.ZodOptional<z.ZodEnum<{
|
|
@@ -449,6 +449,7 @@ declare const CameraStatusSchema: z.ZodObject<{
|
|
|
449
449
|
notifications: "notifications";
|
|
450
450
|
"object-detection": "object-detection";
|
|
451
451
|
"device-audio": "device-audio";
|
|
452
|
+
"broker-audio": "broker-audio";
|
|
452
453
|
}>>>;
|
|
453
454
|
fetchedAt: z.ZodNumber;
|
|
454
455
|
}, z.core.$strip>;
|
|
@@ -1099,6 +1100,7 @@ export declare const pipelineOrchestratorCapability: {
|
|
|
1099
1100
|
notifications: "notifications";
|
|
1100
1101
|
"object-detection": "object-detection";
|
|
1101
1102
|
"device-audio": "device-audio";
|
|
1103
|
+
"broker-audio": "broker-audio";
|
|
1102
1104
|
}>;
|
|
1103
1105
|
label: z.ZodString;
|
|
1104
1106
|
costWhenOff: z.ZodString;
|
|
@@ -1124,6 +1126,8 @@ export declare const pipelineOrchestratorCapability: {
|
|
|
1124
1126
|
}, z.core.$strip>, z.ZodObject<{
|
|
1125
1127
|
kind: z.ZodLiteral<"camera-mask">;
|
|
1126
1128
|
capName: z.ZodString;
|
|
1129
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
1130
|
+
kind: z.ZodLiteral<"broker-audio-mute">;
|
|
1127
1131
|
}, z.core.$strip>], "kind">;
|
|
1128
1132
|
}, z.core.$strip>>>;
|
|
1129
1133
|
fetchedAt: z.ZodNumber;
|
|
@@ -1151,6 +1155,7 @@ export declare const pipelineOrchestratorCapability: {
|
|
|
1151
1155
|
notifications: "notifications";
|
|
1152
1156
|
"object-detection": "object-detection";
|
|
1153
1157
|
"device-audio": "device-audio";
|
|
1158
|
+
"broker-audio": "broker-audio";
|
|
1154
1159
|
}>;
|
|
1155
1160
|
enabled: z.ZodBoolean;
|
|
1156
1161
|
}, z.core.$strip>, z.ZodObject<{
|
|
@@ -1164,6 +1169,7 @@ export declare const pipelineOrchestratorCapability: {
|
|
|
1164
1169
|
notifications: "notifications";
|
|
1165
1170
|
"object-detection": "object-detection";
|
|
1166
1171
|
"device-audio": "device-audio";
|
|
1172
|
+
"broker-audio": "broker-audio";
|
|
1167
1173
|
}>;
|
|
1168
1174
|
label: z.ZodString;
|
|
1169
1175
|
costWhenOff: z.ZodString;
|
|
@@ -1189,6 +1195,8 @@ export declare const pipelineOrchestratorCapability: {
|
|
|
1189
1195
|
}, z.core.$strip>, z.ZodObject<{
|
|
1190
1196
|
kind: z.ZodLiteral<"camera-mask">;
|
|
1191
1197
|
capName: z.ZodString;
|
|
1198
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
1199
|
+
kind: z.ZodLiteral<"broker-audio-mute">;
|
|
1192
1200
|
}, z.core.$strip>], "kind">;
|
|
1193
1201
|
}, z.core.$strip>>>;
|
|
1194
1202
|
fetchedAt: z.ZodNumber;
|
|
@@ -1310,6 +1318,7 @@ export declare const pipelineOrchestratorCapability: {
|
|
|
1310
1318
|
notifications: "notifications";
|
|
1311
1319
|
"object-detection": "object-detection";
|
|
1312
1320
|
"device-audio": "device-audio";
|
|
1321
|
+
"broker-audio": "broker-audio";
|
|
1313
1322
|
}>>>;
|
|
1314
1323
|
fetchedAt: z.ZodNumber;
|
|
1315
1324
|
}, z.core.$strip>, import("./capability-definition.js").CapabilityMethodKind>;
|
|
@@ -1429,6 +1438,7 @@ export declare const pipelineOrchestratorCapability: {
|
|
|
1429
1438
|
notifications: "notifications";
|
|
1430
1439
|
"object-detection": "object-detection";
|
|
1431
1440
|
"device-audio": "device-audio";
|
|
1441
|
+
"broker-audio": "broker-audio";
|
|
1432
1442
|
}>>>;
|
|
1433
1443
|
fetchedAt: z.ZodNumber;
|
|
1434
1444
|
}, z.core.$strip>>>, import("./capability-definition.js").CapabilityMethodKind>;
|
|
@@ -317,6 +317,8 @@ export declare const BrokerStatsSchema: z.ZodObject<{
|
|
|
317
317
|
channels: z.ZodNumber;
|
|
318
318
|
supported: z.ZodBoolean;
|
|
319
319
|
}, z.core.$strip>>>;
|
|
320
|
+
audioMuted: z.ZodOptional<z.ZodBoolean>;
|
|
321
|
+
audioMutedDropped: z.ZodOptional<z.ZodNumber>;
|
|
320
322
|
}, z.core.$strip>;
|
|
321
323
|
/**
|
|
322
324
|
* Exporter-facing "profile restream" entry. Returned by
|
|
@@ -50,8 +50,8 @@ declare const ServerBootModeSchema: z.ZodEnum<{
|
|
|
50
50
|
*/
|
|
51
51
|
declare const ServerUpdateStateSchema: z.ZodEnum<{
|
|
52
52
|
idle: "idle";
|
|
53
|
-
checking: "checking";
|
|
54
53
|
staging: "staging";
|
|
54
|
+
checking: "checking";
|
|
55
55
|
"pending-restart": "pending-restart";
|
|
56
56
|
"awaiting-confirmation": "awaiting-confirmation";
|
|
57
57
|
}>;
|
|
@@ -123,8 +123,8 @@ declare const ServerPackageStatusSchema: z.ZodObject<{
|
|
|
123
123
|
}>;
|
|
124
124
|
updateState: z.ZodEnum<{
|
|
125
125
|
idle: "idle";
|
|
126
|
-
checking: "checking";
|
|
127
126
|
staging: "staging";
|
|
127
|
+
checking: "checking";
|
|
128
128
|
"pending-restart": "pending-restart";
|
|
129
129
|
"awaiting-confirmation": "awaiting-confirmation";
|
|
130
130
|
}>;
|
|
@@ -189,8 +189,8 @@ export declare const serverManagementCapability: {
|
|
|
189
189
|
}>;
|
|
190
190
|
updateState: z.ZodEnum<{
|
|
191
191
|
idle: "idle";
|
|
192
|
-
checking: "checking";
|
|
193
192
|
staging: "staging";
|
|
193
|
+
checking: "checking";
|
|
194
194
|
"pending-restart": "pending-restart";
|
|
195
195
|
"awaiting-confirmation": "awaiting-confirmation";
|
|
196
196
|
}>;
|
|
@@ -689,6 +689,8 @@ export declare const streamBrokerCapability: {
|
|
|
689
689
|
channels: z.ZodNumber;
|
|
690
690
|
supported: z.ZodBoolean;
|
|
691
691
|
}, z.core.$strip>>>;
|
|
692
|
+
audioMuted: z.ZodOptional<z.ZodBoolean>;
|
|
693
|
+
audioMutedDropped: z.ZodOptional<z.ZodNumber>;
|
|
692
694
|
}, z.core.$strip>, import("./capability-definition.js").CapabilityMethodKind>;
|
|
693
695
|
/**
|
|
694
696
|
* Force a one-shot probe of a single source stream: transiently dial the
|
|
@@ -1151,6 +1153,36 @@ export declare const streamBrokerCapability: {
|
|
|
1151
1153
|
readonly isRtspEnabled: import("./capability-definition.js").CapabilityMethodSchema<z.ZodObject<{
|
|
1152
1154
|
brokerId: z.ZodString;
|
|
1153
1155
|
}, z.core.$strip>, z.ZodBoolean, import("./capability-definition.js").CapabilityMethodKind>;
|
|
1156
|
+
/**
|
|
1157
|
+
* ── Per-device audio-plane policy (D83) ───────────────────────────
|
|
1158
|
+
*
|
|
1159
|
+
* The BROKER-side mute: while `muted`, this node distributes none of
|
|
1160
|
+
* the device's audio, on any plane it serves — live (WebRTC / encoded
|
|
1161
|
+
* subscribers) AND recording (the RTSP restreamer the recorder pulls,
|
|
1162
|
+
* which additionally serves the video-only SDP so the recorder's ffmpeg
|
|
1163
|
+
* never declares an audio stream it will not receive).
|
|
1164
|
+
*
|
|
1165
|
+
* Keyed by `deviceId`, not `brokerId`: every one of a camera's streams
|
|
1166
|
+
* carries the same microphone, and a per-stream answer would let main
|
|
1167
|
+
* and sub disagree about whether the camera is silent.
|
|
1168
|
+
*
|
|
1169
|
+
* The state lives in the broker's existing `DeviceOverride` blob — no
|
|
1170
|
+
* new store — and the mute is applied to a fresh broker at creation, so
|
|
1171
|
+
* a restart, a re-dial or a catalog republish never un-mutes a camera.
|
|
1172
|
+
*/
|
|
1173
|
+
readonly getDeviceAudioMute: import("./capability-definition.js").CapabilityMethodSchema<z.ZodObject<{
|
|
1174
|
+
deviceId: z.ZodNumber;
|
|
1175
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
1176
|
+
muted: z.ZodBoolean;
|
|
1177
|
+
appliedBrokers: z.ZodNumber;
|
|
1178
|
+
}, z.core.$strip>, import("./capability-definition.js").CapabilityMethodKind>;
|
|
1179
|
+
readonly setDeviceAudioMute: import("./capability-definition.js").CapabilityMethodSchema<z.ZodObject<{
|
|
1180
|
+
deviceId: z.ZodNumber;
|
|
1181
|
+
muted: z.ZodBoolean;
|
|
1182
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
1183
|
+
muted: z.ZodBoolean;
|
|
1184
|
+
appliedBrokers: z.ZodNumber;
|
|
1185
|
+
}, z.core.$strip>, "mutation">;
|
|
1154
1186
|
};
|
|
1155
1187
|
readonly events: {
|
|
1156
1188
|
readonly onCamStreamDemand: import("./capability-definition.js").CapabilityEventSchema<z.ZodObject<{
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fmp4BoxSplitter — cut an ffmpeg fragmented-MP4 byte stream into the units a
|
|
3
|
+
* consumer of fragments actually needs: ONE initialisation segment
|
|
4
|
+
* (`ftyp`+`moov`) followed by one unit per `moof`+`mdat` pair.
|
|
5
|
+
*
|
|
6
|
+
* This is the shape HomeKit Secure Video's `CameraRecordingDelegate` yields
|
|
7
|
+
* (`RecordingPacket` #0 is the initialisation, the rest are media fragments),
|
|
8
|
+
* and it is the same shape a CamStack clip source wants — which is the point.
|
|
9
|
+
* One fragmenter, two consumers ([roadmap 4b](../../../../../docs/roadmap.md)).
|
|
10
|
+
* The splitter therefore knows about NEITHER of them: it emits units, and
|
|
11
|
+
* {@link import('./fmp4-fragment-plane.js').Fmp4FragmentPlane} decides who gets
|
|
12
|
+
* them.
|
|
13
|
+
*
|
|
14
|
+
* ## Pure, streaming, bounded
|
|
15
|
+
*
|
|
16
|
+
* No I/O, no timers, no ffmpeg. `push` takes whatever bytes arrived and returns
|
|
17
|
+
* whatever units completed — a caller may hand it one byte at a time or a
|
|
18
|
+
* megabyte, and the units are identical either way (asserted). Memory is
|
|
19
|
+
* bounded by {@link Fmp4SplitterOptions.maxUnitBytes}: the splitter holds at
|
|
20
|
+
* most one in-progress unit plus a partial box, and a stream that exceeds the
|
|
21
|
+
* bound FAULTS rather than growing, because the alternative on a live 24/7
|
|
22
|
+
* source is the broker's heap.
|
|
23
|
+
*
|
|
24
|
+
* ## The box rules, from ISO/IEC 14496-12
|
|
25
|
+
*
|
|
26
|
+
* A box header is `size:uint32` + `type:4 chars`. `size == 1` means the real
|
|
27
|
+
* size is a `uint64` in the next 8 bytes; `size == 0` means "to end of file".
|
|
28
|
+
* A size-0 box is a FAULT here and not a special case: it cannot be cut into
|
|
29
|
+
* fragments at all, and pretending otherwise would produce one enormous unit
|
|
30
|
+
* whose bytes never arrive.
|
|
31
|
+
*
|
|
32
|
+
* ## What it does NOT do
|
|
33
|
+
*
|
|
34
|
+
* It never inspects `tfdt`/`trun` and never computes a fragment's duration. The
|
|
35
|
+
* fragment length is decided upstream by the muxer (`-min_frag_duration` plus a
|
|
36
|
+
* key-frame grid, see `@camstack/types` `ffmpeg/invocation.ts`), and a duration
|
|
37
|
+
* parsed here would be a SECOND opinion about the same fact — the failure mode
|
|
38
|
+
* D67 exists to prevent. A consumer that must verify conformance measures the
|
|
39
|
+
* arrival cadence, which is the thing it actually cares about.
|
|
40
|
+
*/
|
|
41
|
+
/** `init` is the `ftyp`+`moov` head; `fragment` is one `moof`+`mdat` pair. */
|
|
42
|
+
export type Fmp4UnitKind = 'init' | 'fragment';
|
|
43
|
+
export interface Fmp4Unit {
|
|
44
|
+
readonly kind: Fmp4UnitKind;
|
|
45
|
+
/** A standalone copy — the splitter never hands out a view into its buffer. */
|
|
46
|
+
readonly data: Uint8Array;
|
|
47
|
+
/** `0` for the initialisation segment, then `1, 2, 3…` per fragment. */
|
|
48
|
+
readonly sequence: number;
|
|
49
|
+
}
|
|
50
|
+
export interface Fmp4SplitterOptions {
|
|
51
|
+
/**
|
|
52
|
+
* The most bytes one unit may reach before the stream is declared unusable.
|
|
53
|
+
* A 4-second 1080p fragment is a few hundred KB; the default leaves three
|
|
54
|
+
* orders of magnitude of headroom and still bounds a source that is not
|
|
55
|
+
* really fMP4 (a plain MP4 with a trailing `moov`, say, whose single `mdat`
|
|
56
|
+
* is the entire recording).
|
|
57
|
+
*/
|
|
58
|
+
readonly maxUnitBytes?: number;
|
|
59
|
+
}
|
|
60
|
+
export declare class Fmp4BoxSplitter {
|
|
61
|
+
private readonly maxUnitBytes;
|
|
62
|
+
/** Bytes of the CURRENT unit plus any partial box after it. */
|
|
63
|
+
private buffer;
|
|
64
|
+
/** Where the current unit starts inside {@link buffer}. */
|
|
65
|
+
private unitStart;
|
|
66
|
+
/** Where the box scanner has reached inside {@link buffer}. */
|
|
67
|
+
private cursor;
|
|
68
|
+
private state;
|
|
69
|
+
private nextSequence;
|
|
70
|
+
private faultReason;
|
|
71
|
+
private readonly interstitial;
|
|
72
|
+
constructor(options?: Fmp4SplitterOptions);
|
|
73
|
+
/**
|
|
74
|
+
* Non-null once the stream cannot be split. The splitter emits nothing
|
|
75
|
+
* further, so a caller polls this to kill the child rather than watching a
|
|
76
|
+
* silent stall — a fragmenter that quietly stops producing looks exactly like
|
|
77
|
+
* a camera with no motion.
|
|
78
|
+
*/
|
|
79
|
+
get fault(): string | null;
|
|
80
|
+
/** Bytes currently held. The memory bound, observable rather than asserted. */
|
|
81
|
+
get pendingBytes(): number;
|
|
82
|
+
/**
|
|
83
|
+
* Top-level box types seen BETWEEN fragments and discarded — `mfra`, `free`,
|
|
84
|
+
* a stray `sidx`. Reported rather than dropped in silence: they are legal and
|
|
85
|
+
* useless to a fragment consumer, but a type nobody expected showing up here
|
|
86
|
+
* is the first symptom of a muxer that is not writing what we think it is.
|
|
87
|
+
*/
|
|
88
|
+
get discardedInterstitialTypes(): readonly string[];
|
|
89
|
+
/**
|
|
90
|
+
* Feed bytes; get back whatever units completed. Returns `[]` once faulted.
|
|
91
|
+
*/
|
|
92
|
+
push(chunk: Uint8Array): readonly Fmp4Unit[];
|
|
93
|
+
private append;
|
|
94
|
+
/** Consume every COMPLETE top-level box now in the buffer. */
|
|
95
|
+
private drainBoxes;
|
|
96
|
+
/**
|
|
97
|
+
* Apply one box to the state machine. Returns a unit when this box CLOSED
|
|
98
|
+
* one, `null` otherwise.
|
|
99
|
+
*/
|
|
100
|
+
private consumeBox;
|
|
101
|
+
/**
|
|
102
|
+
* Parse the header at {@link cursor}, or `null` when too few bytes have
|
|
103
|
+
* arrived to know. Faults on a size the splitter cannot honour.
|
|
104
|
+
*/
|
|
105
|
+
private readHeader;
|
|
106
|
+
private emit;
|
|
107
|
+
/**
|
|
108
|
+
* Drop everything already emitted or discarded. Without this the buffer is
|
|
109
|
+
* the whole stream and the process dies in hours, not minutes.
|
|
110
|
+
*/
|
|
111
|
+
private compact;
|
|
112
|
+
private fail;
|
|
113
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fmp4FragmentChild — one ffmpeg writing a fragmented MP4 to stdout, split into
|
|
3
|
+
* units and fanned out on a {@link Fmp4FragmentPlane}.
|
|
4
|
+
*
|
|
5
|
+
* ## Why this lives in the PRIMITIVE and not in the broker
|
|
6
|
+
*
|
|
7
|
+
* It was written as the stream-broker's third egress transport, on the premise
|
|
8
|
+
* ([D80](../../../../docs/decisions/adr-0080.md)) that its consumers would be
|
|
9
|
+
* in-process with the broker and could hold a live plane object. **They are
|
|
10
|
+
* not.** `addon-export-hap` — the HKSV recording delegate, the first real
|
|
11
|
+
* consumer — is a `hub-only` addon in its OWN runner ([D2](../../../../docs/decisions/adr-0002.md)),
|
|
12
|
+
* and a plane cannot cross a process boundary. The choice was a second copy of
|
|
13
|
+
* the splitter in the exporter, or one copy here beside the sink that produces
|
|
14
|
+
* the bytes. The sink, the splitter, the plane and this child are one primitive
|
|
15
|
+
* ([D84](../../../../docs/decisions/adr-0084.md)); `EgressTranscodeManager`
|
|
16
|
+
* keeps only what is broker business — sharing key, refcount, release grace.
|
|
17
|
+
*
|
|
18
|
+
* It is deliberately the same shape as `TranscodeEgress` (the dial transport)
|
|
19
|
+
* and `createPushChild` (the push one), because the three failures that cost
|
|
20
|
+
* sessions on those paths are the same three here:
|
|
21
|
+
*
|
|
22
|
+
* - **It resolves on the first UNIT, never on the first byte.** A child whose
|
|
23
|
+
* bytes do not parse as fMP4 is a child that will never produce a fragment,
|
|
24
|
+
* and resolving on bytes hands the caller a handle nothing feeds.
|
|
25
|
+
* - **It retries once in SOFTWARE.** `-hwaccel vaapi` fails to create a device
|
|
26
|
+
* inside the stream-broker runner on the live hub (`code=244`), and the push
|
|
27
|
+
* transport was dark for exactly as long as it lacked this retry.
|
|
28
|
+
* - **A child that dies AFTER going live says so.** Silence reads as "it is
|
|
29
|
+
* running"; the plane must be ended so a consumer's generator completes
|
|
30
|
+
* rather than hanging on a producer that no longer exists.
|
|
31
|
+
*
|
|
32
|
+
* Two consumers: `EgressTranscodeManager.acquireFragments` (the broker, for a
|
|
33
|
+
* future CamStack clip source — roadmap 4b half 2) and `HksvFragmentSource` in
|
|
34
|
+
* `addon-export-hap`. It stays unreachable from `acquireEgressTranscode`'s
|
|
35
|
+
* request schema on purpose (see `egressTransportFromRequest`): a plane is a
|
|
36
|
+
* live object, so an off-node caller could not use one anyway.
|
|
37
|
+
*/
|
|
38
|
+
import type { spawn as nodeSpawn } from 'node:child_process';
|
|
39
|
+
import type { IScopedLogger } from '../interfaces/logging.js';
|
|
40
|
+
import type { FfmpegInvocation } from './invocation.js';
|
|
41
|
+
import type { Fmp4FragmentPlane } from './fmp4-fragment-plane.js';
|
|
42
|
+
export interface Fmp4FragmentChildDeps {
|
|
43
|
+
readonly logger: IScopedLogger;
|
|
44
|
+
readonly ffmpegBinaryPath: string;
|
|
45
|
+
readonly spawnFn: typeof nodeSpawn;
|
|
46
|
+
/** How long the child may produce no UNIT before it is declared dead. */
|
|
47
|
+
readonly firstUnitTimeoutMs?: number;
|
|
48
|
+
/** The child exited, or its output stopped parsing, AFTER it went live. */
|
|
49
|
+
readonly onChildExit?: (error: Error) => void;
|
|
50
|
+
}
|
|
51
|
+
export interface Fmp4FragmentChildArgs {
|
|
52
|
+
readonly sourceId: string;
|
|
53
|
+
/** Every line about this child is grouped per camera, without exception. */
|
|
54
|
+
readonly deviceId: number;
|
|
55
|
+
/** The negotiated fragment length — argv only; the splitter never reads it. */
|
|
56
|
+
readonly fragmentMs: number;
|
|
57
|
+
/** The invocation minus the sink, which this child owns. */
|
|
58
|
+
readonly invocation: Omit<FfmpegInvocation, 'sink' | 'audioSidecar'>;
|
|
59
|
+
/** Where the units go. Owned by the caller, ended by this child. */
|
|
60
|
+
readonly plane: Fmp4FragmentPlane;
|
|
61
|
+
}
|
|
62
|
+
export declare class Fmp4FragmentChild {
|
|
63
|
+
private readonly deps;
|
|
64
|
+
private readonly args;
|
|
65
|
+
private child;
|
|
66
|
+
private splitter;
|
|
67
|
+
private stopped;
|
|
68
|
+
private unitsOut;
|
|
69
|
+
private activeHwAccel;
|
|
70
|
+
constructor(deps: Fmp4FragmentChildDeps, args: Fmp4FragmentChildArgs);
|
|
71
|
+
/** Spawn, and resolve once the INIT segment has been cut out of stdout. */
|
|
72
|
+
start(): Promise<void>;
|
|
73
|
+
/** The backend the child ACTUALLY ran with — `null` for software. */
|
|
74
|
+
activeDecodeHwAccel(): string | null;
|
|
75
|
+
/** Kill ffmpeg and end the plane. Idempotent. */
|
|
76
|
+
stop(): Promise<void>;
|
|
77
|
+
private spawnAttempt;
|
|
78
|
+
/**
|
|
79
|
+
* The byte stream stopped being splittable. Not recoverable — the splitter
|
|
80
|
+
* cannot resynchronise mid-box — so the child is a corpse and every consumer
|
|
81
|
+
* has to be told, loudly, with the reason.
|
|
82
|
+
*/
|
|
83
|
+
private onFault;
|
|
84
|
+
private killChild;
|
|
85
|
+
}
|