@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.
Files changed (36) hide show
  1. package/dist/addon.js +8 -2
  2. package/dist/addon.mjs +8 -2
  3. package/dist/capabilities/index.d.ts +5 -2
  4. package/dist/capabilities/osd-manager.cap.d.ts +900 -0
  5. package/dist/capabilities/pipeline-analytics.cap.d.ts +171 -4
  6. package/dist/capabilities/pipeline-orchestrator.cap.d.ts +10 -0
  7. package/dist/capabilities/schemas/streaming-shared.d.ts +2 -0
  8. package/dist/capabilities/server-management.cap.d.ts +3 -3
  9. package/dist/capabilities/stream-broker.cap.d.ts +32 -0
  10. package/dist/ffmpeg/fmp4-box-splitter.d.ts +113 -0
  11. package/dist/ffmpeg/fmp4-fragment-child.d.ts +85 -0
  12. package/dist/ffmpeg/fmp4-fragment-plane.d.ts +142 -0
  13. package/dist/ffmpeg/invocation.d.ts +4 -2
  14. package/dist/fmp4-box-splitter-B53u9-Nu.mjs +615 -0
  15. package/dist/fmp4-box-splitter-BkWH7O3L.js +686 -0
  16. package/dist/generated/addon-api.d.ts +85 -0
  17. package/dist/generated/cap-input-defaults.d.ts +1 -1
  18. package/dist/generated/capability-router-map.d.ts +5 -2
  19. package/dist/generated/device-proxy.d.ts +3 -1
  20. package/dist/generated/method-access-map.d.ts +1 -1
  21. package/dist/generated/system-proxy.d.ts +2 -0
  22. package/dist/index.d.ts +2 -0
  23. package/dist/index.js +581 -404
  24. package/dist/index.mjs +558 -394
  25. package/dist/interfaces/camera-switches.d.ts +48 -0
  26. package/dist/interfaces/stream-broker.d.ts +18 -0
  27. package/dist/node.d.ts +4 -0
  28. package/dist/node.js +509 -3
  29. package/dist/node.mjs +507 -3
  30. package/dist/notification/schedule.d.ts +20 -0
  31. package/dist/{sleep-DtstvzWm.mjs → sleep-cC4Fuup8.mjs} +26 -1
  32. package/dist/{sleep-Bx9IIoT0.js → sleep-eiC10_cX.js} +26 -1
  33. package/dist/types/pipeline-step.d.ts +1 -1
  34. package/package.json +1 -1
  35. package/dist/canonical-hash-7nfBbEqR.mjs +0 -35
  36. 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. Revisit if a flag ever gains an effect that costs storage
1438
- * `deleteTracks` next door is admin for exactly that reason.
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
- * Returns the RESOLVED state of both flags (absent `false`) so a caller
1441
- * can drive its toggle without a re-fetch. Rejects an unknown track.
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
+ }