@camstack/types 1.2.41 → 1.2.43
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 +1 -1
- package/dist/addon.mjs +1 -1
- package/dist/canonical-hash-7nfBbEqR.mjs +35 -0
- package/dist/canonical-hash-BcZHRHIx.js +40 -0
- package/dist/cap-call-context.d.ts +26 -0
- package/dist/capabilities/index.d.ts +5 -5
- package/dist/capabilities/notification-rules.cap.d.ts +41 -0
- package/dist/capabilities/pipeline-analytics.cap.d.ts +92 -4
- package/dist/capabilities/pipeline-orchestrator.cap.d.ts +149 -0
- package/dist/capabilities/pipeline-runner.cap.d.ts +119 -1
- package/dist/capabilities/platform-probe.cap.d.ts +3 -3
- package/dist/capabilities/privacy-mask.cap.d.ts +69 -9
- package/dist/capabilities/recording.cap.d.ts +30 -0
- package/dist/capabilities/snapshot.cap.d.ts +1 -1
- package/dist/capabilities/stream-broker.cap.d.ts +304 -0
- package/dist/capabilities/stream-params.cap.d.ts +8 -4
- package/dist/device/device-profile.d.ts +12 -4
- package/dist/device/system-mirror.d.ts +11 -0
- package/dist/encode-profile.d.ts +2 -0
- package/dist/ffmpeg/encode-defaults.d.ts +107 -0
- package/dist/ffmpeg/hwaccel.d.ts +98 -0
- package/dist/ffmpeg/invocation.d.ts +348 -0
- package/dist/ffmpeg/process.d.ts +135 -0
- package/dist/ffmpeg/sharing-key.d.ts +91 -0
- package/dist/generated/addon-api.d.ts +92 -4
- package/dist/generated/device-proxy.d.ts +2 -2
- package/dist/generated/method-access-map.d.ts +1 -1
- package/dist/generated/system-proxy.d.ts +2 -2
- package/dist/index.d.ts +7 -0
- package/dist/index.js +2069 -42
- package/dist/index.mjs +2006 -43
- package/dist/interfaces/camera-switches.d.ts +375 -0
- package/dist/interfaces/inference-engine.d.ts +24 -3
- package/dist/interfaces/ops-log.d.ts +4 -0
- package/dist/interfaces/pipeline-runner-capability.d.ts +9 -1
- package/dist/node.d.ts +2 -0
- package/dist/node.js +270 -36
- package/dist/node.mjs +269 -36
- package/dist/pipeline/native-lease.d.ts +150 -0
- package/dist/{sleep-DTce7-ch.js → sleep-Bx9IIoT0.js} +43 -1
- package/dist/{sleep-CXimb854.mjs → sleep-DtstvzWm.mjs} +38 -2
- package/dist/utils/addon-id.d.ts +30 -0
- package/package.json +1 -1
|
@@ -279,6 +279,181 @@ export declare const RtpSourceSchema: z.ZodObject<{
|
|
|
279
279
|
pipelineKey: z.ZodString;
|
|
280
280
|
}, z.core.$strip>;
|
|
281
281
|
export type RtpSource = z.infer<typeof RtpSourceSchema>;
|
|
282
|
+
/**
|
|
283
|
+
* The encode request — **structured and serialisable, with NO raw-flag escape
|
|
284
|
+
* hatch.** This is deliberate and it is the one lesson taken from
|
|
285
|
+
* `getStreamWithCodec`: that method's `outputArgs: string[]` is simultaneously
|
|
286
|
+
* its extensibility mechanism AND part of `pipelineKeyFor`'s sharing key, so
|
|
287
|
+
* adding a flag silently forks the shared child, and two consumers that mean
|
|
288
|
+
* the same thing but spell it differently never share. Here every knob is a
|
|
289
|
+
* NAMED field: a new requirement becomes a schema field (and a codegen run),
|
|
290
|
+
* never an opaque array.
|
|
291
|
+
*
|
|
292
|
+
* `inputArgs` / `outputArgs` are omitted from the profile for the same reason.
|
|
293
|
+
* The operator-facing derived-stream transform editor still has them — that is
|
|
294
|
+
* a different surface (`publishCameraStream({ kind: 'derived' })`) with a
|
|
295
|
+
* different purpose (reshaping a badly-behaved SOURCE), and it is unchanged.
|
|
296
|
+
*/
|
|
297
|
+
export declare const EgressEncodeSchema: z.ZodObject<{
|
|
298
|
+
audio: z.ZodUnion<readonly [z.ZodLiteral<"passthrough">, z.ZodObject<{
|
|
299
|
+
codec: z.ZodEnum<{
|
|
300
|
+
copy: "copy";
|
|
301
|
+
opus: "opus";
|
|
302
|
+
aac: "aac";
|
|
303
|
+
pcmu: "pcmu";
|
|
304
|
+
pcma: "pcma";
|
|
305
|
+
}>;
|
|
306
|
+
bitrateKbps: z.ZodOptional<z.ZodNumber>;
|
|
307
|
+
sampleRateHz: z.ZodOptional<z.ZodNumber>;
|
|
308
|
+
channels: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<1>, z.ZodLiteral<2>]>>;
|
|
309
|
+
}, z.core.$strip>]>;
|
|
310
|
+
video: z.ZodObject<{
|
|
311
|
+
codec: z.ZodEnum<{
|
|
312
|
+
h264: "h264";
|
|
313
|
+
h265: "h265";
|
|
314
|
+
copy: "copy";
|
|
315
|
+
}>;
|
|
316
|
+
profile: z.ZodOptional<z.ZodEnum<{
|
|
317
|
+
high: "high";
|
|
318
|
+
baseline: "baseline";
|
|
319
|
+
main: "main";
|
|
320
|
+
}>>;
|
|
321
|
+
level: z.ZodOptional<z.ZodString>;
|
|
322
|
+
width: z.ZodOptional<z.ZodNumber>;
|
|
323
|
+
height: z.ZodOptional<z.ZodNumber>;
|
|
324
|
+
fps: z.ZodOptional<z.ZodNumber>;
|
|
325
|
+
bitrateKbps: z.ZodOptional<z.ZodNumber>;
|
|
326
|
+
gopFrames: z.ZodOptional<z.ZodNumber>;
|
|
327
|
+
bf: z.ZodOptional<z.ZodNumber>;
|
|
328
|
+
preset: z.ZodOptional<z.ZodEnum<{
|
|
329
|
+
fast: "fast";
|
|
330
|
+
ultrafast: "ultrafast";
|
|
331
|
+
superfast: "superfast";
|
|
332
|
+
veryfast: "veryfast";
|
|
333
|
+
faster: "faster";
|
|
334
|
+
medium: "medium";
|
|
335
|
+
}>>;
|
|
336
|
+
tune: z.ZodOptional<z.ZodEnum<{
|
|
337
|
+
zerolatency: "zerolatency";
|
|
338
|
+
film: "film";
|
|
339
|
+
animation: "animation";
|
|
340
|
+
}>>;
|
|
341
|
+
}, z.core.$strip>;
|
|
342
|
+
}, z.core.$strip>;
|
|
343
|
+
export type EgressEncode = z.infer<typeof EgressEncodeSchema>;
|
|
344
|
+
/**
|
|
345
|
+
* How the encoder is bounded. `'tight'` is a one-second VBV window for a
|
|
346
|
+
* consumer whose budget is enforced per second (HomeKit); `'relaxed'` is two
|
|
347
|
+
* seconds, letting a keyframe spike borrow from the next second (a browser,
|
|
348
|
+
* an Echo). Named rather than numeric so the INTENT survives.
|
|
349
|
+
*/
|
|
350
|
+
export declare const EgressRateControlSchema: z.ZodEnum<{
|
|
351
|
+
tight: "tight";
|
|
352
|
+
relaxed: "relaxed";
|
|
353
|
+
}>;
|
|
354
|
+
export type EgressRateControl = z.infer<typeof EgressRateControlSchema>;
|
|
355
|
+
export declare const EgressTranscodeRequestSchema: z.ZodObject<{
|
|
356
|
+
deviceId: z.ZodNumber;
|
|
357
|
+
source: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
358
|
+
kind: z.ZodLiteral<"profile">;
|
|
359
|
+
profile: z.ZodEnum<{
|
|
360
|
+
high: "high";
|
|
361
|
+
mid: "mid";
|
|
362
|
+
low: "low";
|
|
363
|
+
}>;
|
|
364
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
365
|
+
kind: z.ZodLiteral<"cam-stream">;
|
|
366
|
+
camStreamId: z.ZodString;
|
|
367
|
+
}, z.core.$strip>], "kind">;
|
|
368
|
+
encode: z.ZodObject<{
|
|
369
|
+
audio: z.ZodUnion<readonly [z.ZodLiteral<"passthrough">, z.ZodObject<{
|
|
370
|
+
codec: z.ZodEnum<{
|
|
371
|
+
copy: "copy";
|
|
372
|
+
opus: "opus";
|
|
373
|
+
aac: "aac";
|
|
374
|
+
pcmu: "pcmu";
|
|
375
|
+
pcma: "pcma";
|
|
376
|
+
}>;
|
|
377
|
+
bitrateKbps: z.ZodOptional<z.ZodNumber>;
|
|
378
|
+
sampleRateHz: z.ZodOptional<z.ZodNumber>;
|
|
379
|
+
channels: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<1>, z.ZodLiteral<2>]>>;
|
|
380
|
+
}, z.core.$strip>]>;
|
|
381
|
+
video: z.ZodObject<{
|
|
382
|
+
codec: z.ZodEnum<{
|
|
383
|
+
h264: "h264";
|
|
384
|
+
h265: "h265";
|
|
385
|
+
copy: "copy";
|
|
386
|
+
}>;
|
|
387
|
+
profile: z.ZodOptional<z.ZodEnum<{
|
|
388
|
+
high: "high";
|
|
389
|
+
baseline: "baseline";
|
|
390
|
+
main: "main";
|
|
391
|
+
}>>;
|
|
392
|
+
level: z.ZodOptional<z.ZodString>;
|
|
393
|
+
width: z.ZodOptional<z.ZodNumber>;
|
|
394
|
+
height: z.ZodOptional<z.ZodNumber>;
|
|
395
|
+
fps: z.ZodOptional<z.ZodNumber>;
|
|
396
|
+
bitrateKbps: z.ZodOptional<z.ZodNumber>;
|
|
397
|
+
gopFrames: z.ZodOptional<z.ZodNumber>;
|
|
398
|
+
bf: z.ZodOptional<z.ZodNumber>;
|
|
399
|
+
preset: z.ZodOptional<z.ZodEnum<{
|
|
400
|
+
fast: "fast";
|
|
401
|
+
ultrafast: "ultrafast";
|
|
402
|
+
superfast: "superfast";
|
|
403
|
+
veryfast: "veryfast";
|
|
404
|
+
faster: "faster";
|
|
405
|
+
medium: "medium";
|
|
406
|
+
}>>;
|
|
407
|
+
tune: z.ZodOptional<z.ZodEnum<{
|
|
408
|
+
zerolatency: "zerolatency";
|
|
409
|
+
film: "film";
|
|
410
|
+
animation: "animation";
|
|
411
|
+
}>>;
|
|
412
|
+
}, z.core.$strip>;
|
|
413
|
+
}, z.core.$strip>;
|
|
414
|
+
rateControl: z.ZodOptional<z.ZodEnum<{
|
|
415
|
+
tight: "tight";
|
|
416
|
+
relaxed: "relaxed";
|
|
417
|
+
}>>;
|
|
418
|
+
bitstreamFilter: z.ZodOptional<z.ZodEnum<{
|
|
419
|
+
dump_extra: "dump_extra";
|
|
420
|
+
h264_mp4toannexb: "h264_mp4toannexb";
|
|
421
|
+
hevc_mp4toannexb: "hevc_mp4toannexb";
|
|
422
|
+
}>>;
|
|
423
|
+
publishLocally: z.ZodOptional<z.ZodBoolean>;
|
|
424
|
+
pixelFormat: z.ZodOptional<z.ZodEnum<{
|
|
425
|
+
yuv420p: "yuv420p";
|
|
426
|
+
nv12: "nv12";
|
|
427
|
+
}>>;
|
|
428
|
+
decodeHwAccel: z.ZodOptional<z.ZodEnum<{
|
|
429
|
+
none: "none";
|
|
430
|
+
auto: "auto";
|
|
431
|
+
videotoolbox: "videotoolbox";
|
|
432
|
+
vaapi: "vaapi";
|
|
433
|
+
qsv: "qsv";
|
|
434
|
+
cuda: "cuda";
|
|
435
|
+
}>>;
|
|
436
|
+
hostname: z.ZodOptional<z.ZodString>;
|
|
437
|
+
tag: z.ZodOptional<z.ZodString>;
|
|
438
|
+
}, z.core.$strip>;
|
|
439
|
+
export type EgressTranscodeRequest = z.infer<typeof EgressTranscodeRequestSchema>;
|
|
440
|
+
export declare const EgressTranscodeSchema: z.ZodObject<{
|
|
441
|
+
url: z.ZodString;
|
|
442
|
+
pipelineKey: z.ZodString;
|
|
443
|
+
videoCodec: z.ZodEnum<{
|
|
444
|
+
H264: "H264";
|
|
445
|
+
H265: "H265";
|
|
446
|
+
}>;
|
|
447
|
+
resolution: z.ZodObject<{
|
|
448
|
+
width: z.ZodNumber;
|
|
449
|
+
height: z.ZodNumber;
|
|
450
|
+
}, z.core.$strip>;
|
|
451
|
+
transcoded: z.ZodBoolean;
|
|
452
|
+
encoder: z.ZodString;
|
|
453
|
+
decodeHwAccel: z.ZodNullable<z.ZodString>;
|
|
454
|
+
camStreamId: z.ZodNullable<z.ZodString>;
|
|
455
|
+
}, z.core.$strip>;
|
|
456
|
+
export type EgressTranscode = z.infer<typeof EgressTranscodeSchema>;
|
|
282
457
|
export declare const streamBrokerCapability: {
|
|
283
458
|
readonly name: "stream-broker";
|
|
284
459
|
readonly scope: "system";
|
|
@@ -321,6 +496,7 @@ export declare const streamBrokerCapability: {
|
|
|
321
496
|
baseline: "baseline";
|
|
322
497
|
main: "main";
|
|
323
498
|
}>>;
|
|
499
|
+
level: z.ZodOptional<z.ZodString>;
|
|
324
500
|
width: z.ZodOptional<z.ZodNumber>;
|
|
325
501
|
height: z.ZodOptional<z.ZodNumber>;
|
|
326
502
|
fps: z.ZodOptional<z.ZodNumber>;
|
|
@@ -691,6 +867,134 @@ export declare const streamBrokerCapability: {
|
|
|
691
867
|
released: z.ZodBoolean;
|
|
692
868
|
refcount: z.ZodNumber;
|
|
693
869
|
}, z.core.$strip>, "mutation">;
|
|
870
|
+
/**
|
|
871
|
+
* THE ffmpeg primitive. Acquire an encoded stream matching a structured
|
|
872
|
+
* encode plan; the broker builds the argv through the ONE builder
|
|
873
|
+
* (`@camstack/types` `ffmpeg/invocation.ts`), resolves decode hardware from
|
|
874
|
+
* the DECODER ADDON's per-node ranking, and returns a dialable RTSP url.
|
|
875
|
+
*
|
|
876
|
+
* **Refcounted and deduplicated on EXACT match.** Two requesters whose
|
|
877
|
+
* requests produce the same `egressTranscodeSharingKey` receive the SAME
|
|
878
|
+
* `pipelineKey` and the same child process. Nearly-identical requests are
|
|
879
|
+
* NOT merged.
|
|
880
|
+
*
|
|
881
|
+
* **The handle is immutable.** There is deliberately no `reconfigure`
|
|
882
|
+
* method and this one never accepts a `pipelineKey` alongside encode
|
|
883
|
+
* parameters: a consumer whose requirements change releases and
|
|
884
|
+
* re-acquires. A mutable shared handle is exactly what made Alexa's old
|
|
885
|
+
* `derived:alexa-<id>` stream a co-tenant hazard — one consumer's
|
|
886
|
+
* downgrade dragged every other consumer down with it.
|
|
887
|
+
*
|
|
888
|
+
* Placement: unpinned, `classifyCapRoute` serves this hub-in-process
|
|
889
|
+
* (Priority 1). A caller that wants the transcode elsewhere passes
|
|
890
|
+
* `nodePin(nodeId)` and a `hostname` it can dial.
|
|
891
|
+
*/
|
|
892
|
+
readonly acquireEgressTranscode: import("./capability-definition.js").CapabilityMethodSchema<z.ZodObject<{
|
|
893
|
+
deviceId: z.ZodNumber;
|
|
894
|
+
source: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
895
|
+
kind: z.ZodLiteral<"profile">;
|
|
896
|
+
profile: z.ZodEnum<{
|
|
897
|
+
high: "high";
|
|
898
|
+
mid: "mid";
|
|
899
|
+
low: "low";
|
|
900
|
+
}>;
|
|
901
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
902
|
+
kind: z.ZodLiteral<"cam-stream">;
|
|
903
|
+
camStreamId: z.ZodString;
|
|
904
|
+
}, z.core.$strip>], "kind">;
|
|
905
|
+
encode: z.ZodObject<{
|
|
906
|
+
audio: z.ZodUnion<readonly [z.ZodLiteral<"passthrough">, z.ZodObject<{
|
|
907
|
+
codec: z.ZodEnum<{
|
|
908
|
+
copy: "copy";
|
|
909
|
+
opus: "opus";
|
|
910
|
+
aac: "aac";
|
|
911
|
+
pcmu: "pcmu";
|
|
912
|
+
pcma: "pcma";
|
|
913
|
+
}>;
|
|
914
|
+
bitrateKbps: z.ZodOptional<z.ZodNumber>;
|
|
915
|
+
sampleRateHz: z.ZodOptional<z.ZodNumber>;
|
|
916
|
+
channels: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<1>, z.ZodLiteral<2>]>>;
|
|
917
|
+
}, z.core.$strip>]>;
|
|
918
|
+
video: z.ZodObject<{
|
|
919
|
+
codec: z.ZodEnum<{
|
|
920
|
+
h264: "h264";
|
|
921
|
+
h265: "h265";
|
|
922
|
+
copy: "copy";
|
|
923
|
+
}>;
|
|
924
|
+
profile: z.ZodOptional<z.ZodEnum<{
|
|
925
|
+
high: "high";
|
|
926
|
+
baseline: "baseline";
|
|
927
|
+
main: "main";
|
|
928
|
+
}>>;
|
|
929
|
+
level: z.ZodOptional<z.ZodString>;
|
|
930
|
+
width: z.ZodOptional<z.ZodNumber>;
|
|
931
|
+
height: z.ZodOptional<z.ZodNumber>;
|
|
932
|
+
fps: z.ZodOptional<z.ZodNumber>;
|
|
933
|
+
bitrateKbps: z.ZodOptional<z.ZodNumber>;
|
|
934
|
+
gopFrames: z.ZodOptional<z.ZodNumber>;
|
|
935
|
+
bf: z.ZodOptional<z.ZodNumber>;
|
|
936
|
+
preset: z.ZodOptional<z.ZodEnum<{
|
|
937
|
+
fast: "fast";
|
|
938
|
+
ultrafast: "ultrafast";
|
|
939
|
+
superfast: "superfast";
|
|
940
|
+
veryfast: "veryfast";
|
|
941
|
+
faster: "faster";
|
|
942
|
+
medium: "medium";
|
|
943
|
+
}>>;
|
|
944
|
+
tune: z.ZodOptional<z.ZodEnum<{
|
|
945
|
+
zerolatency: "zerolatency";
|
|
946
|
+
film: "film";
|
|
947
|
+
animation: "animation";
|
|
948
|
+
}>>;
|
|
949
|
+
}, z.core.$strip>;
|
|
950
|
+
}, z.core.$strip>;
|
|
951
|
+
rateControl: z.ZodOptional<z.ZodEnum<{
|
|
952
|
+
tight: "tight";
|
|
953
|
+
relaxed: "relaxed";
|
|
954
|
+
}>>;
|
|
955
|
+
bitstreamFilter: z.ZodOptional<z.ZodEnum<{
|
|
956
|
+
dump_extra: "dump_extra";
|
|
957
|
+
h264_mp4toannexb: "h264_mp4toannexb";
|
|
958
|
+
hevc_mp4toannexb: "hevc_mp4toannexb";
|
|
959
|
+
}>>;
|
|
960
|
+
publishLocally: z.ZodOptional<z.ZodBoolean>;
|
|
961
|
+
pixelFormat: z.ZodOptional<z.ZodEnum<{
|
|
962
|
+
yuv420p: "yuv420p";
|
|
963
|
+
nv12: "nv12";
|
|
964
|
+
}>>;
|
|
965
|
+
decodeHwAccel: z.ZodOptional<z.ZodEnum<{
|
|
966
|
+
none: "none";
|
|
967
|
+
auto: "auto";
|
|
968
|
+
videotoolbox: "videotoolbox";
|
|
969
|
+
vaapi: "vaapi";
|
|
970
|
+
qsv: "qsv";
|
|
971
|
+
cuda: "cuda";
|
|
972
|
+
}>>;
|
|
973
|
+
hostname: z.ZodOptional<z.ZodString>;
|
|
974
|
+
tag: z.ZodOptional<z.ZodString>;
|
|
975
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
976
|
+
url: z.ZodString;
|
|
977
|
+
pipelineKey: z.ZodString;
|
|
978
|
+
videoCodec: z.ZodEnum<{
|
|
979
|
+
H264: "H264";
|
|
980
|
+
H265: "H265";
|
|
981
|
+
}>;
|
|
982
|
+
resolution: z.ZodObject<{
|
|
983
|
+
width: z.ZodNumber;
|
|
984
|
+
height: z.ZodNumber;
|
|
985
|
+
}, z.core.$strip>;
|
|
986
|
+
transcoded: z.ZodBoolean;
|
|
987
|
+
encoder: z.ZodString;
|
|
988
|
+
decodeHwAccel: z.ZodNullable<z.ZodString>;
|
|
989
|
+
camStreamId: z.ZodNullable<z.ZodString>;
|
|
990
|
+
}, z.core.$strip>, "mutation">;
|
|
991
|
+
/** Drop one reference. The child dies when the last holder releases. */
|
|
992
|
+
readonly releaseEgressTranscode: import("./capability-definition.js").CapabilityMethodSchema<z.ZodObject<{
|
|
993
|
+
pipelineKey: z.ZodString;
|
|
994
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
995
|
+
released: z.ZodBoolean;
|
|
996
|
+
refcount: z.ZodNumber;
|
|
997
|
+
}, z.core.$strip>, "mutation">;
|
|
694
998
|
/**
|
|
695
999
|
* ── Decoded audio-chunk plane (Phase 5 / D9) ──────────────────────
|
|
696
1000
|
*
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { type InferNativeProvider } from './capability-definition.js';
|
|
3
2
|
import { DeviceType } from '../device/device-type.js';
|
|
3
|
+
import { type InferNativeProvider } from './capability-definition.js';
|
|
4
4
|
/** One of the camera's stream profiles. */
|
|
5
5
|
export declare const StreamProfileSchema: z.ZodEnum<{
|
|
6
6
|
sub: "sub";
|
|
@@ -183,7 +183,13 @@ export declare const StreamParamsOptionsSchema: z.ZodObject<{
|
|
|
183
183
|
}, z.core.$strip>>;
|
|
184
184
|
}, z.core.$strip>;
|
|
185
185
|
/** A partial change to one profile — every field optional; a provider
|
|
186
|
-
* ignores fields it doesn't support.
|
|
186
|
+
* ignores fields it doesn't support.
|
|
187
|
+
*
|
|
188
|
+
* There is deliberately NO `audio` here. It existed until 2026-08-07,
|
|
189
|
+
* reachable from no form and honoured by exactly one provider, while the
|
|
190
|
+
* camera's microphone is a whole-device fact. It now has one writer,
|
|
191
|
+
* `privacyMask.setAudioEnabled`, which writes every profile — see
|
|
192
|
+
* `privacy-mask.cap.ts`. */
|
|
187
193
|
export declare const StreamProfilePatchSchema: z.ZodObject<{
|
|
188
194
|
width: z.ZodOptional<z.ZodNumber>;
|
|
189
195
|
height: z.ZodOptional<z.ZodNumber>;
|
|
@@ -203,7 +209,6 @@ export declare const StreamProfilePatchSchema: z.ZodObject<{
|
|
|
203
209
|
main: "main";
|
|
204
210
|
}>>;
|
|
205
211
|
gop: z.ZodOptional<z.ZodNumber>;
|
|
206
|
-
audio: z.ZodOptional<z.ZodBoolean>;
|
|
207
212
|
}, z.core.$strip>;
|
|
208
213
|
export type StreamParamsStatus = z.infer<typeof StreamParamsStatusSchema>;
|
|
209
214
|
export type StreamParamsOptions = z.infer<typeof StreamParamsOptionsSchema>;
|
|
@@ -316,7 +321,6 @@ export declare const streamParamsCapability: {
|
|
|
316
321
|
main: "main";
|
|
317
322
|
}>>;
|
|
318
323
|
gop: z.ZodOptional<z.ZodNumber>;
|
|
319
|
-
audio: z.ZodOptional<z.ZodBoolean>;
|
|
320
324
|
}, z.core.$strip>;
|
|
321
325
|
}, z.core.$strip>, z.ZodVoid, "mutation">;
|
|
322
326
|
/**
|
|
@@ -120,10 +120,18 @@ export interface DeviceProfile {
|
|
|
120
120
|
*/
|
|
121
121
|
readonly defaults: DeviceProfileDefaults;
|
|
122
122
|
/**
|
|
123
|
-
* Generic settings overrides
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
123
|
+
* Generic settings overrides, keyed by dot-path, intended to be
|
|
124
|
+
* interpreted by the per-driver / per-addon config schema.
|
|
125
|
+
*
|
|
126
|
+
* **Nothing reads this today.** It is a declared extension point with no
|
|
127
|
+
* consumer anywhere in the workspace, so any entry placed here is inert. It
|
|
128
|
+
* shipped populated — `snapshot.minRefreshIntervalSec: 3600` and
|
|
129
|
+
* `streamBroker.preBufferEnabled: false` on the battery profile — which read
|
|
130
|
+
* as live policy and were not: both behaviours are really enforced off
|
|
131
|
+
* `DeviceFeature.BatteryOperated` directly, by `BATTERY_DEFAULT_MAX_AGE_S`
|
|
132
|
+
* in `snapshot.addon.ts` and by `getEffectivePreBufferSec` in
|
|
133
|
+
* `stream-broker-manager.ts`. Emptied 2026-08-07; keep it empty until a
|
|
134
|
+
* reader exists, or an entry here is just documentation that lies.
|
|
127
135
|
*/
|
|
128
136
|
readonly settings: Readonly<Record<string, unknown>>;
|
|
129
137
|
}
|
|
@@ -62,6 +62,17 @@ export interface SystemMirrorApi extends SliceHandleApi {
|
|
|
62
62
|
addonId?: string;
|
|
63
63
|
}): Promise<ReadonlyArray<DeviceInfo>>;
|
|
64
64
|
};
|
|
65
|
+
/**
|
|
66
|
+
* Single-device metadata read. Required — `refreshDeviceMetadata`
|
|
67
|
+
* runs once per `device.registered` / `device.updated` event, and a
|
|
68
|
+
* full-catalog fetch there is what melts the hub during a
|
|
69
|
+
* deploy/restart storm (see `refreshDeviceMetadata`).
|
|
70
|
+
*/
|
|
71
|
+
readonly getDevice: {
|
|
72
|
+
query(input: {
|
|
73
|
+
deviceId: number;
|
|
74
|
+
}): Promise<DeviceInfo | null>;
|
|
75
|
+
};
|
|
65
76
|
};
|
|
66
77
|
readonly deviceState: SliceHandleApi['deviceState'] & {
|
|
67
78
|
readonly getAllSnapshots: {
|
package/dist/encode-profile.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ export declare const VideoEncodeSchema: z.ZodObject<{
|
|
|
22
22
|
baseline: "baseline";
|
|
23
23
|
main: "main";
|
|
24
24
|
}>>;
|
|
25
|
+
level: z.ZodOptional<z.ZodString>;
|
|
25
26
|
width: z.ZodOptional<z.ZodNumber>;
|
|
26
27
|
height: z.ZodOptional<z.ZodNumber>;
|
|
27
28
|
fps: z.ZodOptional<z.ZodNumber>;
|
|
@@ -68,6 +69,7 @@ export declare const EncodeProfileSchema: z.ZodObject<{
|
|
|
68
69
|
baseline: "baseline";
|
|
69
70
|
main: "main";
|
|
70
71
|
}>>;
|
|
72
|
+
level: z.ZodOptional<z.ZodString>;
|
|
71
73
|
width: z.ZodOptional<z.ZodNumber>;
|
|
72
74
|
height: z.ZodOptional<z.ZodNumber>;
|
|
73
75
|
fps: z.ZodOptional<z.ZodNumber>;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ONE home for live-egress encode constants.
|
|
3
|
+
*
|
|
4
|
+
* Before this file there were five sets, in four packages, that nobody could
|
|
5
|
+
* diff: the WebRTC fallback, Alexa's "fallback", the transcode-down ceiling,
|
|
6
|
+
* the broker's audio presets, and HomeKit's inline block. Three of them
|
|
7
|
+
* disagreed about Opus channel count alone. They live here now because the
|
|
8
|
+
* consumers are separate ADDONS and addons may never import each other —
|
|
9
|
+
* `@camstack/types` is the only legal shared home.
|
|
10
|
+
*
|
|
11
|
+
* ## What changed value, and what kept a deliberate divergence
|
|
12
|
+
*
|
|
13
|
+
* KEPT, with the reason:
|
|
14
|
+
* - **VBV window.** HomeKit uses a ONE-second window; the browser and Alexa
|
|
15
|
+
* use TWO. HomeKit's link budget is enforced per second by the controller,
|
|
16
|
+
* so a keyframe allowed to borrow from the next second overshoots and the
|
|
17
|
+
* iOS client drops the stream. A browser wants the opposite: let a keyframe
|
|
18
|
+
* spike, keep the average. Encoded as {@link FfmpegRateControl}, not as two
|
|
19
|
+
* copies of a number.
|
|
20
|
+
* - **`preset`.** HomeKit and the browser use `ultrafast` (the encoder is on
|
|
21
|
+
* the interactive path); Alexa uses `veryfast` (Echo tolerates ~200 ms more
|
|
22
|
+
* latency and the extra quality is visible on a 10" panel).
|
|
23
|
+
* - **`-bsf:v dump_extra`** is HomeKit-only on BOTH the copy and encode
|
|
24
|
+
* branches: HAP negotiates its own SDP and cannot carry out-of-band
|
|
25
|
+
* extradata. Nothing else needs it, and emitting it elsewhere costs bytes.
|
|
26
|
+
*
|
|
27
|
+
* CHANGED — the divergences that were accidents:
|
|
28
|
+
* - **Opus channels.** Was 2 (broker preset), 1 (Alexa), 1 (HomeKit). Now 1
|
|
29
|
+
* everywhere: every consumer here is a camera microphone, which is mono at
|
|
30
|
+
* the source, and encoding a duplicated second channel spent bitrate on
|
|
31
|
+
* nothing. The broker's 2 was the outlier and it is the one that changed.
|
|
32
|
+
* - **Alexa gained `level: '3.1'`.** Its SDP advertises `profile-level-id=
|
|
33
|
+
* 42e01f` and its encoder was unconstrained. Now it matches.
|
|
34
|
+
* - **Alexa GOP.** Was 25 frames at 25 fps (1 s); the browser is 30 at 25 fps
|
|
35
|
+
* (1.2 s). Both are now expressed as `gopSeconds` × fps so the intent is
|
|
36
|
+
* legible; Alexa's numeric GOP is unchanged, the browser's rounds to 30.
|
|
37
|
+
*/
|
|
38
|
+
import type { EncodeProfile } from '../encode-profile.js';
|
|
39
|
+
import type { FfmpegAudioEncodePlan, FfmpegRateControl } from './invocation.js';
|
|
40
|
+
/**
|
|
41
|
+
* The shape every live egress starts from: H.264 Baseline 3.1 at 720p25.
|
|
42
|
+
* Baseline because it is the one profile every consumer in this repo decodes
|
|
43
|
+
* (Echo, iOS, an old browser); 3.1 because that is what the SDPs advertise.
|
|
44
|
+
*/
|
|
45
|
+
export declare const BASE_LIVE_EGRESS_PROFILE: EncodeProfile;
|
|
46
|
+
/**
|
|
47
|
+
* The browser WebRTC viewer's transcode profile. Used only when the broker
|
|
48
|
+
* MUST spawn ffmpeg (source codec ∉ the browser-accepted set — an H.265 source
|
|
49
|
+
* on an H.264-only browser). Audio is `passthrough` because werift carries the
|
|
50
|
+
* audio plane out-of-band; putting it in the video pipeline would encode a
|
|
51
|
+
* track nothing reads.
|
|
52
|
+
*/
|
|
53
|
+
export declare const WEBRTC_EGRESS_PROFILE: EncodeProfile;
|
|
54
|
+
/**
|
|
55
|
+
* Alexa's egress profile. Alexa's audio ALSO rides the WebRTC session's own
|
|
56
|
+
* audio plane (out-of-band), so this is `passthrough` exactly like the browser
|
|
57
|
+
* — see the ADR for why the previous in-band Opus was encoded and discarded.
|
|
58
|
+
*/
|
|
59
|
+
/**
|
|
60
|
+
* Alexa's egress asks for OPUS, and that is a change with a history.
|
|
61
|
+
*
|
|
62
|
+
* The previous profile encoded Opus IN-BAND into an MPEG-TS, where it was
|
|
63
|
+
* discarded: Opus is `stream_type 0x06` and the broker's demuxer maps only
|
|
64
|
+
* `0x0f` (aac), `0x03`/`0x04` (mp2) and `0x81` (ac3). Every frame it produced
|
|
65
|
+
* died at the demuxer, so the encode was replaced with `passthrough` — correct,
|
|
66
|
+
* because paying libopus for nothing is worse than silence.
|
|
67
|
+
*
|
|
68
|
+
* But `passthrough` means the egress emits NO audio at all, and Alexa's audio
|
|
69
|
+
* does not arrive by magic: it rides the published stream, which is what the
|
|
70
|
+
* WebRTC session dials. So the Echo had video and silence either way.
|
|
71
|
+
*
|
|
72
|
+
* Opus here now reaches the egress AUDIO SIDECAR — a separate RTP leg the
|
|
73
|
+
* restreamer grafts onto its SDP — which never touches the MPEG-TS demuxer that
|
|
74
|
+
* killed the in-band attempt. Same codec, different plane, and this one the
|
|
75
|
+
* consumer can actually negotiate.
|
|
76
|
+
*/
|
|
77
|
+
export declare const ALEXA_EGRESS_PROFILE: EncodeProfile;
|
|
78
|
+
/** VBV window for a consumer whose budget is enforced per second (HomeKit). */
|
|
79
|
+
export declare const RATE_CONTROL_TIGHT: FfmpegRateControl;
|
|
80
|
+
/** VBV window for a consumer that tolerates a keyframe spike (browser, Echo). */
|
|
81
|
+
export declare const RATE_CONTROL_RELAXED: FfmpegRateControl;
|
|
82
|
+
/**
|
|
83
|
+
* Transcode-DOWN ceiling: the shape a remote/unhealthy WebRTC session is
|
|
84
|
+
* reduced to. 360p / 400 kbps is the `low` rung of the adaptive ladder — a
|
|
85
|
+
* session steered here is already failing, so the ceiling is chosen to survive
|
|
86
|
+
* a bad link, not to look good.
|
|
87
|
+
*/
|
|
88
|
+
export declare const TRANSCODE_DOWN_MAX_HEIGHT = 360;
|
|
89
|
+
export declare const TRANSCODE_DOWN_MAX_BITRATE_KBPS = 400;
|
|
90
|
+
/**
|
|
91
|
+
* Camera-microphone audio, per codec. Defined in `./invocation.js` (an import
|
|
92
|
+
* cycle otherwise) and re-exported here, which is where to read it.
|
|
93
|
+
*/
|
|
94
|
+
export { AUDIO_PRESETS } from './invocation.js';
|
|
95
|
+
/**
|
|
96
|
+
* HomeKit's two-way audio leg. `lowdelay` + a short frame duration because the
|
|
97
|
+
* leg is interactive; `globalHeader` because HAP owns the SDP and ffmpeg must
|
|
98
|
+
* not put extradata in-band. `sampleRateHz` and `frameDurationMs` are
|
|
99
|
+
* NEGOTIATED per session — the controller picks them — so they are absent here
|
|
100
|
+
* and filled in by the caller.
|
|
101
|
+
*/
|
|
102
|
+
export declare const HAP_AUDIO_BITRATE_KBPS = 24;
|
|
103
|
+
export declare const HAP_AUDIO_BASE: FfmpegAudioEncodePlan;
|
|
104
|
+
/** HomeKit's `-bufsize` for the audio plane — four seconds of its 24 kbps. */
|
|
105
|
+
export declare const HAP_AUDIO_VBV_KBITS: number;
|
|
106
|
+
/** Seconds between forced IDRs on a HomeKit transcode. */
|
|
107
|
+
export declare const HAP_KEYFRAME_INTERVAL_SEC = 4;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decode hardware acceleration for an EGRESS transcode — resolved through the
|
|
3
|
+
* ONE ranking that is known to work on this hardware.
|
|
4
|
+
*
|
|
5
|
+
* ## Why this file exists
|
|
6
|
+
*
|
|
7
|
+
* The repo has two hwaccel opinions and they disagree in the direction that
|
|
8
|
+
* breaks things:
|
|
9
|
+
*
|
|
10
|
+
* - the raw kernel resolver (`ctx.kernel.hwaccel.resolve()`) returns
|
|
11
|
+
* `['qsv','vaapi']` for an Intel host — qsv FIRST;
|
|
12
|
+
* - `addon-decoder-ffmpeg`'s `DECODE_HWACCEL_RANK` puts **vaapi above qsv on
|
|
13
|
+
* purpose**, because on this exact hub a qsv decode child exits `code=171`
|
|
14
|
+
* producing no frames, while vaapi decodes 8 MP h264 at ~50 fps.
|
|
15
|
+
*
|
|
16
|
+
* **The decoder addon is the authority.** It publishes its answer per node as
|
|
17
|
+
* the `probedBestHwaccel@<node>` setting and exposes it on the `decoder` cap's
|
|
18
|
+
* `getInfo` — the same value `AgentLoadService.readNodeDecodeHwaccel` reads to
|
|
19
|
+
* show per-node hwaccel in the UI. This module reads THAT, and deliberately
|
|
20
|
+
* never re-derives a ranking: a second copy of the rank table is exactly how
|
|
21
|
+
* the two opinions appeared. Addons may not import each other, so the read is
|
|
22
|
+
* a `ctx.api.decoder.getInfo` call injected as {@link EgressHwAccelDeps.readDecoderBackend}.
|
|
23
|
+
*
|
|
24
|
+
* ## Why the kernel resolver is still a dependency here
|
|
25
|
+
*
|
|
26
|
+
* It is only ever used to say something in a log line. It is never allowed to
|
|
27
|
+
* SELECT a backend: if the decoder cannot answer, this resolver returns
|
|
28
|
+
* software rather than adopting the kernel's qsv-first order, because that
|
|
29
|
+
* order is the known-bad one. `never silently downgrades` in the spec is the
|
|
30
|
+
* test that holds this.
|
|
31
|
+
*
|
|
32
|
+
* ## The failure mode this must not reproduce
|
|
33
|
+
*
|
|
34
|
+
* A silent fall back to software decode is a FLOW bug on this hub, not a
|
|
35
|
+
* capability limit (vaapi works — 10 concurrent 4K decodes pass). So every
|
|
36
|
+
* path that ends in software calls {@link EgressHwAccelDeps.onFallback}, which
|
|
37
|
+
* the caller logs at `warn` with `tags: { deviceId }`. Silence reads as
|
|
38
|
+
* "hardware was used".
|
|
39
|
+
*/
|
|
40
|
+
/** Why a resolve ended in software decode. */
|
|
41
|
+
export type EgressHwAccelFallbackReason =
|
|
42
|
+
/** The `decoder` cap could not be reached (offline node, cap not mounted). */
|
|
43
|
+
'decoder-unreadable'
|
|
44
|
+
/** The decoder answered, but has not probed a backend on this node yet. */
|
|
45
|
+
| 'decoder-unprobed';
|
|
46
|
+
export interface EgressHwAccelFallback {
|
|
47
|
+
readonly reason: EgressHwAccelFallbackReason;
|
|
48
|
+
/** What the kernel resolver would have said — for the log line ONLY. */
|
|
49
|
+
readonly kernelPreferred: readonly string[];
|
|
50
|
+
readonly error?: string;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* A bounded memo so a per-session resolve is not a per-session cross-process
|
|
54
|
+
* cap call. Owned by the caller (one per broker / exporter), never a module
|
|
55
|
+
* global — a module global would outlive an addon respawn and survive an
|
|
56
|
+
* operator changing the decoder backend.
|
|
57
|
+
*/
|
|
58
|
+
export interface HwAccelCache {
|
|
59
|
+
read(): string | null | undefined;
|
|
60
|
+
write(value: string | null): void;
|
|
61
|
+
}
|
|
62
|
+
export interface HwAccelCacheOptions {
|
|
63
|
+
readonly ttlMs: number;
|
|
64
|
+
readonly now?: () => number;
|
|
65
|
+
}
|
|
66
|
+
export declare function createHwAccelCache(options: HwAccelCacheOptions): HwAccelCache;
|
|
67
|
+
export interface EgressHwAccelDeps {
|
|
68
|
+
/**
|
|
69
|
+
* `ctx.api.decoder.getInfo.query(undefined, nodePin(nodeId)).probedBestHwaccel`
|
|
70
|
+
* — the DECODER ADDON's own answer for THIS node, already re-ranked through
|
|
71
|
+
* `DECODE_HWACCEL_RANK`. Throw or return `''` when it is not knowable.
|
|
72
|
+
*/
|
|
73
|
+
readDecoderBackend(): Promise<string>;
|
|
74
|
+
/**
|
|
75
|
+
* `ctx.kernel.hwaccel.resolve().preferred` — reported in the fallback log so
|
|
76
|
+
* an operator can see the disagreement. NEVER used to select a backend.
|
|
77
|
+
*/
|
|
78
|
+
readKernelPreferred(): Promise<readonly string[]>;
|
|
79
|
+
/** Called on every path that ends in software. The caller logs at `warn`. */
|
|
80
|
+
onFallback(fallback: EgressHwAccelFallback): void;
|
|
81
|
+
/**
|
|
82
|
+
* An explicit operator choice (`decoder.hwaccel`, or a per-consumer pin).
|
|
83
|
+
* `'auto'` is passed through as the literal ffmpeg instruction; `'none'` /
|
|
84
|
+
* `'copy'` force software; anything else pins that backend. Absent ⇒ ask the
|
|
85
|
+
* decoder.
|
|
86
|
+
*/
|
|
87
|
+
readonly override?: string | null;
|
|
88
|
+
readonly cache?: HwAccelCache;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Resolve the `-hwaccel` value for an egress transcode. Hardware is the
|
|
92
|
+
* DEFAULT — an egress that decodes in software on a hub with working vaapi is
|
|
93
|
+
* paying for nothing — and every software outcome is announced.
|
|
94
|
+
*
|
|
95
|
+
* Returns a concrete backend name, the literal `'auto'`, or `null` for
|
|
96
|
+
* software decode (⇒ {@link buildFfmpegArgs} emits no `-hwaccel` at all).
|
|
97
|
+
*/
|
|
98
|
+
export declare function resolveEgressDecodeHwAccel(deps: EgressHwAccelDeps): Promise<string | null>;
|