@ceralive/cerastream 2026.7.4 → 2026.7.6

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.
@@ -8,7 +8,7 @@ export declare const PROTOCOL_VERSION: "cerastream-ipc/1";
8
8
  * additive-only within protocol major `cerastream-ipc/1` (ADR-0002 §4); this value
9
9
  * only moves when the wire schema itself does, in lockstep across both languages.
10
10
  */
11
- export declare const SCHEMA_VERSION: "0.10.0";
11
+ export declare const SCHEMA_VERSION: "0.11.0";
12
12
  /** Runtime dir holding both control + preview sockets. systemd `RuntimeDirectory=cerastream`. */
13
13
  export declare const DEFAULT_IPC_DIR: "/run/cerastream";
14
14
  /** Env override for the IPC dir (tests/dev). Defaults to {@link DEFAULT_IPC_DIR}. */
@@ -63,7 +63,7 @@ export declare const PROFILE_CATALOG_VERSION: "1.0.0";
63
63
  * ignores the field) never applies a semantic the caller assumed. Mirrors the
64
64
  * Rust `ENGINE_FEATURES`.
65
65
  */
66
- export declare const ENGINE_FEATURES: readonly ["video-passthrough"];
66
+ export declare const ENGINE_FEATURES: readonly ["video-passthrough", "input-mode"];
67
67
  /** Engine binary name (systemd-owned; CeraUI never spawns it — ADR-0005). */
68
68
  export declare const CERASTREAM_BIN: "cerastream";
69
69
  export declare const DEFAULT_MIN_BITRATE = 300;
package/dist/constants.js CHANGED
@@ -11,7 +11,7 @@ export const PROTOCOL_VERSION = "cerastream-ipc/1";
11
11
  * additive-only within protocol major `cerastream-ipc/1` (ADR-0002 §4); this value
12
12
  * only moves when the wire schema itself does, in lockstep across both languages.
13
13
  */
14
- export const SCHEMA_VERSION = "0.10.0";
14
+ export const SCHEMA_VERSION = "0.11.0";
15
15
  /** Runtime dir holding both control + preview sockets. systemd `RuntimeDirectory=cerastream`. */
16
16
  export const DEFAULT_IPC_DIR = "/run/cerastream";
17
17
  /** Env override for the IPC dir (tests/dev). Defaults to {@link DEFAULT_IPC_DIR}. */
@@ -72,7 +72,7 @@ export const PROFILE_CATALOG_VERSION = "1.0.0";
72
72
  * ignores the field) never applies a semantic the caller assumed. Mirrors the
73
73
  * Rust `ENGINE_FEATURES`.
74
74
  */
75
- export const ENGINE_FEATURES = ["video-passthrough"];
75
+ export const ENGINE_FEATURES = ["video-passthrough", "input-mode"];
76
76
  /** Engine binary name (systemd-owned; CeraUI never spawns it — ADR-0005). */
77
77
  export const CERASTREAM_BIN = "cerastream";
78
78
  // ---- config defaults (mirror ceracoder, the engine being replaced) ----
package/dist/errors.d.ts CHANGED
@@ -88,8 +88,21 @@ export declare const processErrorCodeSchema: z.ZodEnum<{
88
88
  pipeline_stall: "pipeline_stall";
89
89
  srt_connect_failed: "srt_connect_failed";
90
90
  srt_connection_lost: "srt_connection_lost";
91
+ capture_unrecoverable: "capture_unrecoverable";
91
92
  }>;
92
93
  export type ProcessErrorCode = z.infer<typeof processErrorCodeSchema>;
94
+ /**
95
+ * Why a capture device became `capture_unrecoverable` — the `reason` field of the
96
+ * `error` event. Mirrors the Rust `CaptureUnrecoverableReason`.
97
+ */
98
+ export declare const captureUnrecoverableReasonSchema: z.ZodEnum<{
99
+ node_absent_reprobe_disabled: "node_absent_reprobe_disabled";
100
+ reprobe_no_reenumeration: "reprobe_no_reenumeration";
101
+ reprobe_identity_changed: "reprobe_identity_changed";
102
+ reprobe_node_still_absent: "reprobe_node_still_absent";
103
+ reprobe_unavailable: "reprobe_unavailable";
104
+ }>;
105
+ export type CaptureUnrecoverableReason = z.infer<typeof captureUnrecoverableReasonSchema>;
93
106
  /** Origin of a Tier 2 runtime error (the notification channel CeraUI routes on). */
94
107
  export declare const processErrorSourceSchema: z.ZodEnum<{
95
108
  srtla: "srtla";
package/dist/errors.js CHANGED
@@ -124,6 +124,21 @@ export const processErrorCodeSchema = z.enum([
124
124
  "pipeline_stall",
125
125
  "srt_connect_failed",
126
126
  "srt_connection_lost",
127
+ // TERMINAL, unlike capture_video_error: the device is still on the bus but the
128
+ // engine has exhausted recovery, so a UI that keeps showing "reconnecting"
129
+ // would be lying. Always carries a captureUnrecoverableReasonSchema `reason`.
130
+ "capture_unrecoverable",
131
+ ]);
132
+ /**
133
+ * Why a capture device became `capture_unrecoverable` — the `reason` field of the
134
+ * `error` event. Mirrors the Rust `CaptureUnrecoverableReason`.
135
+ */
136
+ export const captureUnrecoverableReasonSchema = z.enum([
137
+ "node_absent_reprobe_disabled",
138
+ "reprobe_no_reenumeration",
139
+ "reprobe_identity_changed",
140
+ "reprobe_node_still_absent",
141
+ "reprobe_unavailable",
127
142
  ]);
128
143
  /** Origin of a Tier 2 runtime error (the notification channel CeraUI routes on). */
129
144
  export const processErrorSourceSchema = z.enum(["srtla", "engine"]);
package/dist/events.d.ts CHANGED
@@ -20,6 +20,37 @@ export declare const activeEncodeSchema: z.ZodObject<{
20
20
  pipeline_playing: z.ZodOptional<z.ZodBoolean>;
21
21
  }, z.core.$strip>;
22
22
  export type ActiveEncode = z.infer<typeof activeEncodeSchema>;
23
+ /**
24
+ * What this session's PREVIEW branch is actually encoding with (additive),
25
+ * reported on the `status` event as `preview_encoder_realized`. A separate
26
+ * realized fact from {@link activeEncodeSchema}, which describes the EGRESS
27
+ * encode — the two are different graph branches and are independently absent.
28
+ *
29
+ * Present only while a session has a preview branch spliced into its graph (the
30
+ * branch exists for the life of the session even while its valve is closed).
31
+ * ABSENT means "no preview branch, or a legacy emitter" — which is NOT
32
+ * `mode: "software"` (a branch exists and encodes in software) and NOT
33
+ * `preview.preview_hw_capability === false` (the board publishes no preview
34
+ * encoder at all). Those three states must never be defaulted into each other.
35
+ *
36
+ * `mode` is the ACTIVE mode. The REQUESTED mode is deliberately not an engine
37
+ * fact: it lives in CeraUI's persisted config.
38
+ */
39
+ export declare const previewEncoderRealizedSchema: z.ZodObject<{
40
+ selected_element: z.ZodOptional<z.ZodString>;
41
+ realized_element: z.ZodString;
42
+ mode: z.ZodEnum<{
43
+ software: "software";
44
+ hardware: "hardware";
45
+ }>;
46
+ fallback_reason: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
47
+ code: z.ZodLiteral<"factory-missing">;
48
+ }, z.core.$strip>, z.ZodObject<{
49
+ code: z.ZodLiteral<"property-failure">;
50
+ property: z.ZodString;
51
+ }, z.core.$strip>], "code">>;
52
+ }, z.core.$strip>;
53
+ export type PreviewEncoderRealized = z.infer<typeof previewEncoderRealizedSchema>;
23
54
  /**
24
55
  * `status` event — stream state change + heartbeat. The trailing fields are
25
56
  * additive: the `buffering*` / `spooled_bytes` / `data_headroom_bytes` /
@@ -28,7 +59,9 @@ export type ActiveEncode = z.infer<typeof activeEncodeSchema>;
28
59
  * `active_profile` (srt-receive-profiles) reports the profile/config the stream is
29
60
  * running under and is absent on a legacy emitter or a bare state-only heartbeat;
30
61
  * `active_encode` (0.4.0) reports the resolved runtime encode and is absent on a
31
- * legacy emitter or a bare state-only heartbeat.
62
+ * legacy emitter or a bare state-only heartbeat; `preview_encoder_realized`
63
+ * reports the PREVIEW branch's own encoder and is absent when the session has no
64
+ * preview branch.
32
65
  */
33
66
  export declare const statusEventSchema: z.ZodObject<{
34
67
  type: z.ZodLiteral<"status">;
@@ -59,6 +92,20 @@ export declare const statusEventSchema: z.ZodObject<{
59
92
  frames_emitted: z.ZodOptional<z.ZodNumber>;
60
93
  pipeline_playing: z.ZodOptional<z.ZodBoolean>;
61
94
  }, z.core.$strip>>;
95
+ preview_encoder_realized: z.ZodOptional<z.ZodObject<{
96
+ selected_element: z.ZodOptional<z.ZodString>;
97
+ realized_element: z.ZodString;
98
+ mode: z.ZodEnum<{
99
+ software: "software";
100
+ hardware: "hardware";
101
+ }>;
102
+ fallback_reason: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
103
+ code: z.ZodLiteral<"factory-missing">;
104
+ }, z.core.$strip>, z.ZodObject<{
105
+ code: z.ZodLiteral<"property-failure">;
106
+ property: z.ZodString;
107
+ }, z.core.$strip>], "code">>;
108
+ }, z.core.$strip>>;
62
109
  }, z.core.$strip>;
63
110
  /** Payload of a {@link statusEventSchema} event. */
64
111
  export type StatusEvent = z.infer<typeof statusEventSchema>;
@@ -115,6 +162,25 @@ export declare const deviceEventSchema: z.ZodObject<{
115
162
  test: "test";
116
163
  network: "network";
117
164
  }>>;
165
+ modes: z.ZodOptional<z.ZodArray<z.ZodObject<{
166
+ media_type: z.ZodString;
167
+ pipeline_kind: z.ZodEnum<{
168
+ audio: "audio";
169
+ hdmi: "hdmi";
170
+ uvc_h264: "uvc_h264";
171
+ uvc_h265: "uvc_h265";
172
+ mjpeg: "mjpeg";
173
+ camlink: "camlink";
174
+ test: "test";
175
+ network: "network";
176
+ }>;
177
+ caps: z.ZodArray<z.ZodObject<{
178
+ width: z.ZodOptional<z.ZodNumber>;
179
+ height: z.ZodOptional<z.ZodNumber>;
180
+ framerate: z.ZodOptional<z.ZodString>;
181
+ media_type: z.ZodOptional<z.ZodString>;
182
+ }, z.core.$strip>>;
183
+ }, z.core.$strip>>>;
118
184
  alsa_card_id: z.ZodOptional<z.ZodString>;
119
185
  product_name: z.ZodOptional<z.ZodString>;
120
186
  transport: z.ZodOptional<z.ZodEnum<{
@@ -164,12 +230,14 @@ export declare const runtimeErrorEventSchema: z.ZodObject<{
164
230
  pipeline_stall: "pipeline_stall";
165
231
  srt_connect_failed: "srt_connect_failed";
166
232
  srt_connection_lost: "srt_connection_lost";
233
+ capture_unrecoverable: "capture_unrecoverable";
167
234
  }>;
168
235
  source: z.ZodEnum<{
169
236
  srtla: "srtla";
170
237
  engine: "engine";
171
238
  }>;
172
239
  reason: z.ZodOptional<z.ZodString>;
240
+ selected: z.ZodOptional<z.ZodBoolean>;
173
241
  }, z.core.$strip>;
174
242
  /** Payload of a {@link runtimeErrorEventSchema} event. */
175
243
  export type RuntimeErrorEvent = z.infer<typeof runtimeErrorEventSchema>;
@@ -268,6 +336,20 @@ export declare const eventParamsSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
268
336
  frames_emitted: z.ZodOptional<z.ZodNumber>;
269
337
  pipeline_playing: z.ZodOptional<z.ZodBoolean>;
270
338
  }, z.core.$strip>>;
339
+ preview_encoder_realized: z.ZodOptional<z.ZodObject<{
340
+ selected_element: z.ZodOptional<z.ZodString>;
341
+ realized_element: z.ZodString;
342
+ mode: z.ZodEnum<{
343
+ software: "software";
344
+ hardware: "hardware";
345
+ }>;
346
+ fallback_reason: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
347
+ code: z.ZodLiteral<"factory-missing">;
348
+ }, z.core.$strip>, z.ZodObject<{
349
+ code: z.ZodLiteral<"property-failure">;
350
+ property: z.ZodString;
351
+ }, z.core.$strip>], "code">>;
352
+ }, z.core.$strip>>;
271
353
  }, z.core.$strip>, z.ZodObject<{
272
354
  type: z.ZodLiteral<"switch">;
273
355
  seq: z.ZodNumber;
@@ -312,6 +394,25 @@ export declare const eventParamsSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
312
394
  test: "test";
313
395
  network: "network";
314
396
  }>>;
397
+ modes: z.ZodOptional<z.ZodArray<z.ZodObject<{
398
+ media_type: z.ZodString;
399
+ pipeline_kind: z.ZodEnum<{
400
+ audio: "audio";
401
+ hdmi: "hdmi";
402
+ uvc_h264: "uvc_h264";
403
+ uvc_h265: "uvc_h265";
404
+ mjpeg: "mjpeg";
405
+ camlink: "camlink";
406
+ test: "test";
407
+ network: "network";
408
+ }>;
409
+ caps: z.ZodArray<z.ZodObject<{
410
+ width: z.ZodOptional<z.ZodNumber>;
411
+ height: z.ZodOptional<z.ZodNumber>;
412
+ framerate: z.ZodOptional<z.ZodString>;
413
+ media_type: z.ZodOptional<z.ZodString>;
414
+ }, z.core.$strip>>;
415
+ }, z.core.$strip>>>;
315
416
  alsa_card_id: z.ZodOptional<z.ZodString>;
316
417
  product_name: z.ZodOptional<z.ZodString>;
317
418
  transport: z.ZodOptional<z.ZodEnum<{
@@ -346,12 +447,14 @@ export declare const eventParamsSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
346
447
  pipeline_stall: "pipeline_stall";
347
448
  srt_connect_failed: "srt_connect_failed";
348
449
  srt_connection_lost: "srt_connection_lost";
450
+ capture_unrecoverable: "capture_unrecoverable";
349
451
  }>;
350
452
  source: z.ZodEnum<{
351
453
  srtla: "srtla";
352
454
  engine: "engine";
353
455
  }>;
354
456
  reason: z.ZodOptional<z.ZodString>;
457
+ selected: z.ZodOptional<z.ZodBoolean>;
355
458
  }, z.core.$strip>, z.ZodObject<{
356
459
  type: z.ZodLiteral<"preview">;
357
460
  seq: z.ZodNumber;
@@ -424,6 +527,20 @@ export declare const cerastreamEventSchema: z.ZodObject<{
424
527
  frames_emitted: z.ZodOptional<z.ZodNumber>;
425
528
  pipeline_playing: z.ZodOptional<z.ZodBoolean>;
426
529
  }, z.core.$strip>>;
530
+ preview_encoder_realized: z.ZodOptional<z.ZodObject<{
531
+ selected_element: z.ZodOptional<z.ZodString>;
532
+ realized_element: z.ZodString;
533
+ mode: z.ZodEnum<{
534
+ software: "software";
535
+ hardware: "hardware";
536
+ }>;
537
+ fallback_reason: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
538
+ code: z.ZodLiteral<"factory-missing">;
539
+ }, z.core.$strip>, z.ZodObject<{
540
+ code: z.ZodLiteral<"property-failure">;
541
+ property: z.ZodString;
542
+ }, z.core.$strip>], "code">>;
543
+ }, z.core.$strip>>;
427
544
  }, z.core.$strip>, z.ZodObject<{
428
545
  type: z.ZodLiteral<"switch">;
429
546
  seq: z.ZodNumber;
@@ -468,6 +585,25 @@ export declare const cerastreamEventSchema: z.ZodObject<{
468
585
  test: "test";
469
586
  network: "network";
470
587
  }>>;
588
+ modes: z.ZodOptional<z.ZodArray<z.ZodObject<{
589
+ media_type: z.ZodString;
590
+ pipeline_kind: z.ZodEnum<{
591
+ audio: "audio";
592
+ hdmi: "hdmi";
593
+ uvc_h264: "uvc_h264";
594
+ uvc_h265: "uvc_h265";
595
+ mjpeg: "mjpeg";
596
+ camlink: "camlink";
597
+ test: "test";
598
+ network: "network";
599
+ }>;
600
+ caps: z.ZodArray<z.ZodObject<{
601
+ width: z.ZodOptional<z.ZodNumber>;
602
+ height: z.ZodOptional<z.ZodNumber>;
603
+ framerate: z.ZodOptional<z.ZodString>;
604
+ media_type: z.ZodOptional<z.ZodString>;
605
+ }, z.core.$strip>>;
606
+ }, z.core.$strip>>>;
471
607
  alsa_card_id: z.ZodOptional<z.ZodString>;
472
608
  product_name: z.ZodOptional<z.ZodString>;
473
609
  transport: z.ZodOptional<z.ZodEnum<{
@@ -502,12 +638,14 @@ export declare const cerastreamEventSchema: z.ZodObject<{
502
638
  pipeline_stall: "pipeline_stall";
503
639
  srt_connect_failed: "srt_connect_failed";
504
640
  srt_connection_lost: "srt_connection_lost";
641
+ capture_unrecoverable: "capture_unrecoverable";
505
642
  }>;
506
643
  source: z.ZodEnum<{
507
644
  srtla: "srtla";
508
645
  engine: "engine";
509
646
  }>;
510
647
  reason: z.ZodOptional<z.ZodString>;
648
+ selected: z.ZodOptional<z.ZodBoolean>;
511
649
  }, z.core.$strip>, z.ZodObject<{
512
650
  type: z.ZodLiteral<"preview">;
513
651
  seq: z.ZodNumber;
@@ -584,6 +722,20 @@ export declare const eventSchemas: {
584
722
  frames_emitted: z.ZodOptional<z.ZodNumber>;
585
723
  pipeline_playing: z.ZodOptional<z.ZodBoolean>;
586
724
  }, z.core.$strip>>;
725
+ preview_encoder_realized: z.ZodOptional<z.ZodObject<{
726
+ selected_element: z.ZodOptional<z.ZodString>;
727
+ realized_element: z.ZodString;
728
+ mode: z.ZodEnum<{
729
+ software: "software";
730
+ hardware: "hardware";
731
+ }>;
732
+ fallback_reason: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
733
+ code: z.ZodLiteral<"factory-missing">;
734
+ }, z.core.$strip>, z.ZodObject<{
735
+ code: z.ZodLiteral<"property-failure">;
736
+ property: z.ZodString;
737
+ }, z.core.$strip>], "code">>;
738
+ }, z.core.$strip>>;
587
739
  }, z.core.$strip>;
588
740
  readonly switch: z.ZodObject<{
589
741
  type: z.ZodLiteral<"switch">;
@@ -630,6 +782,25 @@ export declare const eventSchemas: {
630
782
  test: "test";
631
783
  network: "network";
632
784
  }>>;
785
+ modes: z.ZodOptional<z.ZodArray<z.ZodObject<{
786
+ media_type: z.ZodString;
787
+ pipeline_kind: z.ZodEnum<{
788
+ audio: "audio";
789
+ hdmi: "hdmi";
790
+ uvc_h264: "uvc_h264";
791
+ uvc_h265: "uvc_h265";
792
+ mjpeg: "mjpeg";
793
+ camlink: "camlink";
794
+ test: "test";
795
+ network: "network";
796
+ }>;
797
+ caps: z.ZodArray<z.ZodObject<{
798
+ width: z.ZodOptional<z.ZodNumber>;
799
+ height: z.ZodOptional<z.ZodNumber>;
800
+ framerate: z.ZodOptional<z.ZodString>;
801
+ media_type: z.ZodOptional<z.ZodString>;
802
+ }, z.core.$strip>>;
803
+ }, z.core.$strip>>>;
633
804
  alsa_card_id: z.ZodOptional<z.ZodString>;
634
805
  product_name: z.ZodOptional<z.ZodString>;
635
806
  transport: z.ZodOptional<z.ZodEnum<{
@@ -667,12 +838,14 @@ export declare const eventSchemas: {
667
838
  pipeline_stall: "pipeline_stall";
668
839
  srt_connect_failed: "srt_connect_failed";
669
840
  srt_connection_lost: "srt_connection_lost";
841
+ capture_unrecoverable: "capture_unrecoverable";
670
842
  }>;
671
843
  source: z.ZodEnum<{
672
844
  srtla: "srtla";
673
845
  engine: "engine";
674
846
  }>;
675
847
  reason: z.ZodOptional<z.ZodString>;
848
+ selected: z.ZodOptional<z.ZodBoolean>;
676
849
  }, z.core.$strip>;
677
850
  readonly preview: z.ZodObject<{
678
851
  type: z.ZodLiteral<"preview">;
package/dist/events.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { processErrorCodeSchema, processErrorSourceSchema, } from "./errors.js";
3
- import { captureDeviceSchema, configChangePhaseSchema, inputModeSchema, mediaClassSchema, streamStateSchema, } from "./types.js";
3
+ import { captureDeviceSchema, configChangePhaseSchema, inputModeSchema, mediaClassSchema, previewEncodeFallbackSchema, previewEncodeModeSchema, streamStateSchema, } from "./types.js";
4
4
  // Server → client `event` notifications, delivered after `subscribe-events`
5
5
  // (schema.md "Events"). Each event is the inner `params` of an rpcEventSchema:
6
6
  // { type, seq, … }. `type` discriminates; `seq` is a per-type monotonic counter.
@@ -25,6 +25,28 @@ export const activeEncodeSchema = z.object({
25
25
  frames_emitted: z.number().int().nonnegative().optional(), // 0.7.0: monotonic egress-buffer counter; advances across status heartbeats while frames flow
26
26
  pipeline_playing: z.boolean().optional(), // 0.7.0: pipeline is in GStreamer PLAYING (false during an in-process reconnect)
27
27
  });
28
+ /**
29
+ * What this session's PREVIEW branch is actually encoding with (additive),
30
+ * reported on the `status` event as `preview_encoder_realized`. A separate
31
+ * realized fact from {@link activeEncodeSchema}, which describes the EGRESS
32
+ * encode — the two are different graph branches and are independently absent.
33
+ *
34
+ * Present only while a session has a preview branch spliced into its graph (the
35
+ * branch exists for the life of the session even while its valve is closed).
36
+ * ABSENT means "no preview branch, or a legacy emitter" — which is NOT
37
+ * `mode: "software"` (a branch exists and encodes in software) and NOT
38
+ * `preview.preview_hw_capability === false` (the board publishes no preview
39
+ * encoder at all). Those three states must never be defaulted into each other.
40
+ *
41
+ * `mode` is the ACTIVE mode. The REQUESTED mode is deliberately not an engine
42
+ * fact: it lives in CeraUI's persisted config.
43
+ */
44
+ export const previewEncoderRealizedSchema = z.object({
45
+ selected_element: z.string().optional(), // the element the platform's HAL descriptor publishes; absent ⇒ this board publishes none
46
+ realized_element: z.string(), // the element actually built into the preview branch, e.g. "x264enc"/"mpph264enc"
47
+ mode: previewEncodeModeSchema, // the encoder family actually realized — never the request
48
+ fallback_reason: previewEncodeFallbackSchema.optional(), // why a hardware request is in software; absent is the normal case
49
+ });
28
50
  /**
29
51
  * `status` event — stream state change + heartbeat. The trailing fields are
30
52
  * additive: the `buffering*` / `spooled_bytes` / `data_headroom_bytes` /
@@ -33,7 +55,9 @@ export const activeEncodeSchema = z.object({
33
55
  * `active_profile` (srt-receive-profiles) reports the profile/config the stream is
34
56
  * running under and is absent on a legacy emitter or a bare state-only heartbeat;
35
57
  * `active_encode` (0.4.0) reports the resolved runtime encode and is absent on a
36
- * legacy emitter or a bare state-only heartbeat.
58
+ * legacy emitter or a bare state-only heartbeat; `preview_encoder_realized`
59
+ * reports the PREVIEW branch's own encoder and is absent when the session has no
60
+ * preview branch.
37
61
  */
38
62
  export const statusEventSchema = z.object({
39
63
  type: z.literal("status"),
@@ -47,6 +71,7 @@ export const statusEventSchema = z.object({
47
71
  data_headroom_bytes: z.number().int().nonnegative().optional(),
48
72
  disk_warning: z.boolean().optional(),
49
73
  active_encode: activeEncodeSchema.optional(),
74
+ preview_encoder_realized: previewEncoderRealizedSchema.optional(),
50
75
  });
51
76
  /**
52
77
  * `switch` event — active input changed (manual or failover). `media_class` is
@@ -93,6 +118,7 @@ export const runtimeErrorEventSchema = z.object({
93
118
  code: processErrorCodeSchema, // Tier 2 (errors.ts)
94
119
  source: processErrorSourceSchema,
95
120
  reason: z.string().optional(), // structured replacement for the stderr <reason>
121
+ selected: z.boolean().optional(), // additive (0.11.0): the degraded input is the operator's OWN selection, so the stream will not move itself and only they can pick another — a materially different operator response than a degraded non-selected input, and one to branch on rather than string-match. Absent ⇒ false
96
122
  });
97
123
  /** `preview` event — a preview-session lifecycle/phase change. */
98
124
  export const previewEventSchema = z.object({
@@ -42,6 +42,16 @@ export declare const startParamsSchema: z.ZodObject<{
42
42
  force: "force";
43
43
  off: "off";
44
44
  }>>;
45
+ input_mode: z.ZodOptional<z.ZodEnum<{
46
+ audio: "audio";
47
+ hdmi: "hdmi";
48
+ uvc_h264: "uvc_h264";
49
+ uvc_h265: "uvc_h265";
50
+ mjpeg: "mjpeg";
51
+ camlink: "camlink";
52
+ test: "test";
53
+ network: "network";
54
+ }>>;
45
55
  }, z.core.$strip>;
46
56
  export type StartParams = z.infer<typeof startParamsSchema>;
47
57
  export declare const startResultSchema: z.ZodObject<{
@@ -85,6 +95,10 @@ export declare const reloadConfigParamsSchema: z.ZodObject<{
85
95
  delay_ms_signed: z.ZodOptional<z.ZodNumber>;
86
96
  meter_device: z.ZodOptional<z.ZodNullable<z.ZodString>>;
87
97
  }, z.core.$strip>>;
98
+ preview_encode: z.ZodOptional<z.ZodEnum<{
99
+ software: "software";
100
+ hardware: "hardware";
101
+ }>>;
88
102
  }, z.core.$strip>;
89
103
  export type ReloadConfigParams = z.infer<typeof reloadConfigParamsSchema>;
90
104
  export declare const reloadConfigResultSchema: z.ZodObject<{
@@ -106,6 +120,10 @@ export declare const reloadConfigResultSchema: z.ZodObject<{
106
120
  delay_ms_signed: z.ZodOptional<z.ZodNumber>;
107
121
  meter_device: z.ZodOptional<z.ZodNullable<z.ZodString>>;
108
122
  }, z.core.$strip>>;
123
+ preview_encode: z.ZodOptional<z.ZodEnum<{
124
+ software: "software";
125
+ hardware: "hardware";
126
+ }>>;
109
127
  }, z.core.$strip>;
110
128
  bitrate_control: z.ZodOptional<z.ZodEnum<{
111
129
  encoder: "encoder";
@@ -193,6 +211,25 @@ export declare const listDevicesResultSchema: z.ZodObject<{
193
211
  test: "test";
194
212
  network: "network";
195
213
  }>>;
214
+ modes: z.ZodOptional<z.ZodArray<z.ZodObject<{
215
+ media_type: z.ZodString;
216
+ pipeline_kind: z.ZodEnum<{
217
+ audio: "audio";
218
+ hdmi: "hdmi";
219
+ uvc_h264: "uvc_h264";
220
+ uvc_h265: "uvc_h265";
221
+ mjpeg: "mjpeg";
222
+ camlink: "camlink";
223
+ test: "test";
224
+ network: "network";
225
+ }>;
226
+ caps: z.ZodArray<z.ZodObject<{
227
+ width: z.ZodOptional<z.ZodNumber>;
228
+ height: z.ZodOptional<z.ZodNumber>;
229
+ framerate: z.ZodOptional<z.ZodString>;
230
+ media_type: z.ZodOptional<z.ZodString>;
231
+ }, z.core.$strip>>;
232
+ }, z.core.$strip>>>;
196
233
  alsa_card_id: z.ZodOptional<z.ZodString>;
197
234
  product_name: z.ZodOptional<z.ZodString>;
198
235
  transport: z.ZodOptional<z.ZodEnum<{
@@ -328,6 +365,7 @@ export declare const previewAvailabilitySchema: z.ZodObject<{
328
365
  enabled: z.ZodBoolean;
329
366
  port: z.ZodOptional<z.ZodNumber>;
330
367
  bound: z.ZodBoolean;
368
+ preview_hw_capability: z.ZodOptional<z.ZodBoolean>;
331
369
  }, z.core.$strip>;
332
370
  export type PreviewAvailability = z.infer<typeof previewAvailabilitySchema>;
333
371
  export declare const getCapabilitiesResultSchema: z.ZodObject<{
@@ -375,6 +413,7 @@ export declare const getCapabilitiesResultSchema: z.ZodObject<{
375
413
  enabled: z.ZodBoolean;
376
414
  port: z.ZodOptional<z.ZodNumber>;
377
415
  bound: z.ZodBoolean;
416
+ preview_hw_capability: z.ZodOptional<z.ZodBoolean>;
378
417
  }, z.core.$strip>>;
379
418
  network_embedded_audio: z.ZodOptional<z.ZodBoolean>;
380
419
  features: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -389,6 +428,16 @@ export declare const changeConfigParamsSchema: z.ZodObject<{
389
428
  h265: "h265";
390
429
  }>>;
391
430
  input_id: z.ZodOptional<z.ZodString>;
431
+ input_mode: z.ZodOptional<z.ZodEnum<{
432
+ audio: "audio";
433
+ hdmi: "hdmi";
434
+ uvc_h264: "uvc_h264";
435
+ uvc_h265: "uvc_h265";
436
+ mjpeg: "mjpeg";
437
+ camlink: "camlink";
438
+ test: "test";
439
+ network: "network";
440
+ }>>;
392
441
  }, z.core.$strip>;
393
442
  export type ChangeConfigParams = z.infer<typeof changeConfigParamsSchema>;
394
443
  export declare const changeConfigResultSchema: z.ZodObject<{
@@ -480,6 +529,16 @@ export declare const requestSchemas: {
480
529
  force: "force";
481
530
  off: "off";
482
531
  }>>;
532
+ input_mode: z.ZodOptional<z.ZodEnum<{
533
+ audio: "audio";
534
+ hdmi: "hdmi";
535
+ uvc_h264: "uvc_h264";
536
+ uvc_h265: "uvc_h265";
537
+ mjpeg: "mjpeg";
538
+ camlink: "camlink";
539
+ test: "test";
540
+ network: "network";
541
+ }>>;
483
542
  }, z.core.$strip>;
484
543
  readonly result: z.ZodObject<{
485
544
  session_id: z.ZodString;
@@ -523,6 +582,10 @@ export declare const requestSchemas: {
523
582
  delay_ms_signed: z.ZodOptional<z.ZodNumber>;
524
583
  meter_device: z.ZodOptional<z.ZodNullable<z.ZodString>>;
525
584
  }, z.core.$strip>>;
585
+ preview_encode: z.ZodOptional<z.ZodEnum<{
586
+ software: "software";
587
+ hardware: "hardware";
588
+ }>>;
526
589
  }, z.core.$strip>;
527
590
  readonly result: z.ZodObject<{
528
591
  applied: z.ZodObject<{
@@ -543,6 +606,10 @@ export declare const requestSchemas: {
543
606
  delay_ms_signed: z.ZodOptional<z.ZodNumber>;
544
607
  meter_device: z.ZodOptional<z.ZodNullable<z.ZodString>>;
545
608
  }, z.core.$strip>>;
609
+ preview_encode: z.ZodOptional<z.ZodEnum<{
610
+ software: "software";
611
+ hardware: "hardware";
612
+ }>>;
546
613
  }, z.core.$strip>;
547
614
  bitrate_control: z.ZodOptional<z.ZodEnum<{
548
615
  encoder: "encoder";
@@ -614,6 +681,25 @@ export declare const requestSchemas: {
614
681
  test: "test";
615
682
  network: "network";
616
683
  }>>;
684
+ modes: z.ZodOptional<z.ZodArray<z.ZodObject<{
685
+ media_type: z.ZodString;
686
+ pipeline_kind: z.ZodEnum<{
687
+ audio: "audio";
688
+ hdmi: "hdmi";
689
+ uvc_h264: "uvc_h264";
690
+ uvc_h265: "uvc_h265";
691
+ mjpeg: "mjpeg";
692
+ camlink: "camlink";
693
+ test: "test";
694
+ network: "network";
695
+ }>;
696
+ caps: z.ZodArray<z.ZodObject<{
697
+ width: z.ZodOptional<z.ZodNumber>;
698
+ height: z.ZodOptional<z.ZodNumber>;
699
+ framerate: z.ZodOptional<z.ZodString>;
700
+ media_type: z.ZodOptional<z.ZodString>;
701
+ }, z.core.$strip>>;
702
+ }, z.core.$strip>>>;
617
703
  alsa_card_id: z.ZodOptional<z.ZodString>;
618
704
  product_name: z.ZodOptional<z.ZodString>;
619
705
  transport: z.ZodOptional<z.ZodEnum<{
package/dist/messages.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { AUDIO_DELAY_MAX_MS } from "./constants.js";
3
3
  import { helloParamsSchema, helloResultSchema, } from "./envelope.js";
4
- import { balancerAlgorithmSchema, bitrateControlSchema, captureDeviceSchema, cerastreamConfigSchema, configChangePhaseSchema, inputModeSchema, mediaClassSchema, previewTierSchema, streamStateSchema, videoCodecSchema, } from "./types.js";
4
+ import { balancerAlgorithmSchema, bitrateControlSchema, captureDeviceKindSchema, captureDeviceSchema, cerastreamConfigSchema, configChangePhaseSchema, inputModeSchema, mediaClassSchema, previewEncodeModeSchema, previewTierSchema, streamStateSchema, videoCodecSchema, } from "./types.js";
5
5
  // The eight v1 control methods (schema.md "v1 messages"). Each is matched
6
6
  // byte-for-byte to schema.md: the JSON shape on the wire is the contract.
7
7
  // ---- 1. start ----
@@ -45,6 +45,12 @@ export const reloadConfigParamsSchema = z.object({
45
45
  meter_device: z.string().nullable().optional(),
46
46
  })
47
47
  .optional(),
48
+ // Absent leaves the engine's current preview-encoder selection unchanged. This
49
+ // belongs on `reload-config` rather than `change-config`: the preview encoder is
50
+ // fixed when a session graph is built, so it takes effect on the NEXT stream
51
+ // session, and routing it through the transactional method would restart a live
52
+ // stream to change a preview setting.
53
+ preview_encode: previewEncodeModeSchema.optional(),
48
54
  });
49
55
  export const reloadConfigResultSchema = z.object({
50
56
  applied: reloadConfigParamsSchema, // post-clamp values actually applied
@@ -179,6 +185,15 @@ export const previewAvailabilitySchema = z.object({
179
185
  enabled: z.boolean(),
180
186
  port: z.number().int().min(1).max(65535).optional(), // present when a listener is (or would be) bound
181
187
  bound: z.boolean(),
188
+ // Whether this PLATFORM publishes a hardware preview encoder at all — the fact
189
+ // that decides whether a hardware-preview toggle may be offered. It rides the
190
+ // capability fetch rather than stream status because a consumer reads
191
+ // capabilities while IDLE, which is exactly when the operator picks the setting.
192
+ // Three readings, none interchangeable: absent ⇒ legacy engine, capability
193
+ // UNKNOWN (never assume false); false ⇒ this board publishes none, hide the
194
+ // toggle; true ⇒ it does. Independent of whether any session is realizing it
195
+ // (`status.preview_encoder_realized`).
196
+ preview_hw_capability: z.boolean().optional(),
182
197
  });
183
198
  export const getCapabilitiesResultSchema = z.object({
184
199
  platform: platformCapsSchema,
@@ -207,6 +222,13 @@ export const changeConfigParamsSchema = z
207
222
  framerate: z.number().optional(),
208
223
  codec: videoCodecSchema.optional(),
209
224
  input_id: z.string().optional(),
225
+ // Switching a LIVE device between a libuvc family (uvc_h264/uvc_h265) and
226
+ // the v4l2 mjpeg family is NOT a plain rebuild: libuvc detaches uvcvideo, so
227
+ // the /dev/videoN node the mjpeg leg needs does not exist until the hold is
228
+ // released — and it then returns asynchronously, possibly renumbered. The
229
+ // engine runs a release → re-enumeration-barrier → open transaction and
230
+ // rolls back honestly (reason: mode_barrier_timeout) if the barrier expires.
231
+ input_mode: captureDeviceKindSchema.optional(),
210
232
  })
211
233
  .refine((delta) => Object.keys(delta).length > 0, {
212
234
  message: "change-config delta must carry at least one field",
package/dist/types.d.ts CHANGED
@@ -27,6 +27,18 @@ export declare const previewTierSchema: z.ZodEnum<{
27
27
  webrtc: "webrtc";
28
28
  }>;
29
29
  export type PreviewTier = z.infer<typeof previewTierSchema>;
30
+ export declare const previewEncodeModeSchema: z.ZodEnum<{
31
+ software: "software";
32
+ hardware: "hardware";
33
+ }>;
34
+ export type PreviewEncodeMode = z.infer<typeof previewEncodeModeSchema>;
35
+ export declare const previewEncodeFallbackSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
36
+ code: z.ZodLiteral<"factory-missing">;
37
+ }, z.core.$strip>, z.ZodObject<{
38
+ code: z.ZodLiteral<"property-failure">;
39
+ property: z.ZodString;
40
+ }, z.core.$strip>], "code">;
41
+ export type PreviewEncodeFallback = z.infer<typeof previewEncodeFallbackSchema>;
30
42
  export declare const videoCodecSchema: z.ZodEnum<{
31
43
  h264: "h264";
32
44
  h265: "h265";
@@ -164,6 +176,16 @@ export declare const cerastreamConfigSchema: z.ZodObject<{
164
176
  force: "force";
165
177
  off: "off";
166
178
  }>>;
179
+ input_mode: z.ZodOptional<z.ZodEnum<{
180
+ audio: "audio";
181
+ hdmi: "hdmi";
182
+ uvc_h264: "uvc_h264";
183
+ uvc_h265: "uvc_h265";
184
+ mjpeg: "mjpeg";
185
+ camlink: "camlink";
186
+ test: "test";
187
+ network: "network";
188
+ }>>;
167
189
  }, z.core.$strip>;
168
190
  export type CerastreamConfig = z.infer<typeof cerastreamConfigSchema>;
169
191
  export type PartialCerastreamConfig = z.input<typeof cerastreamConfigSchema>;
@@ -180,6 +202,26 @@ export declare const captureCapSchema: z.ZodObject<{
180
202
  media_type: z.ZodOptional<z.ZodString>;
181
203
  }, z.core.$strip>;
182
204
  export type CaptureCap = z.infer<typeof captureCapSchema>;
205
+ export declare const captureModeSchema: z.ZodObject<{
206
+ media_type: z.ZodString;
207
+ pipeline_kind: z.ZodEnum<{
208
+ audio: "audio";
209
+ hdmi: "hdmi";
210
+ uvc_h264: "uvc_h264";
211
+ uvc_h265: "uvc_h265";
212
+ mjpeg: "mjpeg";
213
+ camlink: "camlink";
214
+ test: "test";
215
+ network: "network";
216
+ }>;
217
+ caps: z.ZodArray<z.ZodObject<{
218
+ width: z.ZodOptional<z.ZodNumber>;
219
+ height: z.ZodOptional<z.ZodNumber>;
220
+ framerate: z.ZodOptional<z.ZodString>;
221
+ media_type: z.ZodOptional<z.ZodString>;
222
+ }, z.core.$strip>>;
223
+ }, z.core.$strip>;
224
+ export type CaptureMode = z.infer<typeof captureModeSchema>;
183
225
  export declare const captureDeviceSchema: z.ZodObject<{
184
226
  input_id: z.ZodString;
185
227
  device_path: z.ZodString;
@@ -204,6 +246,25 @@ export declare const captureDeviceSchema: z.ZodObject<{
204
246
  test: "test";
205
247
  network: "network";
206
248
  }>>;
249
+ modes: z.ZodOptional<z.ZodArray<z.ZodObject<{
250
+ media_type: z.ZodString;
251
+ pipeline_kind: z.ZodEnum<{
252
+ audio: "audio";
253
+ hdmi: "hdmi";
254
+ uvc_h264: "uvc_h264";
255
+ uvc_h265: "uvc_h265";
256
+ mjpeg: "mjpeg";
257
+ camlink: "camlink";
258
+ test: "test";
259
+ network: "network";
260
+ }>;
261
+ caps: z.ZodArray<z.ZodObject<{
262
+ width: z.ZodOptional<z.ZodNumber>;
263
+ height: z.ZodOptional<z.ZodNumber>;
264
+ framerate: z.ZodOptional<z.ZodString>;
265
+ media_type: z.ZodOptional<z.ZodString>;
266
+ }, z.core.$strip>>;
267
+ }, z.core.$strip>>>;
207
268
  alsa_card_id: z.ZodOptional<z.ZodString>;
208
269
  product_name: z.ZodOptional<z.ZodString>;
209
270
  transport: z.ZodOptional<z.ZodEnum<{
package/dist/types.js CHANGED
@@ -14,6 +14,22 @@ export const inputModeSchema = z.enum(["manual", "auto"]);
14
14
  export const balancerAlgorithmSchema = z.enum(["adaptive", "fixed", "aimd"]);
15
15
  export const mediaClassSchema = z.enum(["video", "audio"]);
16
16
  export const previewTierSchema = z.enum(["webcodecs", "webrtc"]);
17
+ // Which encoder the LIVE preview branch uses. Set through `reload-config`
18
+ // (additive) and reported back as the ACTIVE mode on stream status. Applies to
19
+ // the NEXT stream session: the preview encoder is fixed when a session graph is
20
+ // built, so this never swaps an encoder mid-stream.
21
+ export const previewEncodeModeSchema = z.enum(["software", "hardware"]);
22
+ // Why a `hardware` preview request is encoding in software anyway — a tagged
23
+ // union so a consumer branches on `code` and still receives the detail a code
24
+ // carries. `property-failure` names the property the element refused, which is
25
+ // the difference between "hardware preview is broken" and "this image's encoder
26
+ // plugin does not take `rc-mode`". ABSENT is the normal case (software was
27
+ // chosen, hardware was delivered, or the board never offered the toggle) — there
28
+ // is deliberately no `{code:"none"}` sentinel.
29
+ export const previewEncodeFallbackSchema = z.discriminatedUnion("code", [
30
+ z.object({ code: z.literal("factory-missing") }),
31
+ z.object({ code: z.literal("property-failure"), property: z.string() }),
32
+ ]);
17
33
  // Requested egress video codec (0.4.0, additive). Absent ⇒ the engine picks its
18
34
  // platform default. Wire values are the codec id strings the encoder caps also
19
35
  // advertise ("h264"/"h265") — never an encoder element name.
@@ -129,6 +145,7 @@ export const cerastreamConfigSchema = z.object({
129
145
  framerate: z.number().optional(), // additive (0.4.0): fps as a number, e.g. 29.97
130
146
  audio: audioConfigSchema.optional(), // additive (0.4.0): audio device/codec/signed delay
131
147
  video_passthrough: videoPassthroughSchema.optional(), // additive (0.5.0): auto|force|off; absent ⇒ auto
148
+ input_mode: captureDeviceKindSchema.optional(), // additive (0.11.0): which MODE family of the selected device to capture in, naming a list-devices modes[].pipeline_kind. Absent ⇒ the engine's precedence pick (h265 → h264 → mjpeg → raw). The ONLY way to reach the non-precedence family of a dual-format camera; a family the device does not expose is a typed cerastream.params.invalid, never a silent fallback
132
149
  });
133
150
  // convenience defaults for building a config client-side
134
151
  export const DEFAULT_BITRATE_CONFIG = {
@@ -144,13 +161,33 @@ export const captureCapSchema = z.object({
144
161
  framerate: z.string().optional(), // e.g. "30/1"
145
162
  media_type: z.string().optional(), // e.g. "video/x-h265"; additive, absent on legacy producers
146
163
  });
164
+ // One startable capture MODE family of a device (0.11.0, additive).
165
+ //
166
+ // A capture device is not necessarily one format family: the DJI Osmo Pocket 3
167
+ // exposes H.264 AND MJPEG on the SAME /dev/video1 node (selection is a
168
+ // VIDIOC_S_FMT pixelformat choice, not a different node). The scalar
169
+ // captureDevice.kind can only name ONE family, so before this contract a
170
+ // dual-format camera was classified uvc_h264 by precedence and its MJPEG ladder
171
+ // was unreachable — visible, but impossible to start.
172
+ //
173
+ // pipeline_kind is exactly what a caller echoes back as start.input_mode to drive
174
+ // the device in this family. caps is the ladder for THIS family ALONE, never the
175
+ // device-wide union: on the Osmo, H.264 offers 4K at 60/50/48 fps while MJPEG
176
+ // tops out at 30, so a merged list would advertise 4K60 MJPEG — a mode the device
177
+ // cannot deliver.
178
+ export const captureModeSchema = z.object({
179
+ media_type: z.string(), // e.g. "image/jpeg"
180
+ pipeline_kind: captureDeviceKindSchema, // the value start.input_mode takes to select this mode
181
+ caps: z.array(captureCapSchema), // the {w × h × framerate} ladder for THIS media type only
182
+ });
147
183
  export const captureDeviceSchema = z.object({
148
184
  input_id: z.string(), // stable id used by switch-input
149
185
  device_path: z.string(), // e.g. /dev/video0 (dedup key)
150
186
  display_name: z.string(),
151
187
  media_class: mediaClassSchema,
152
188
  caps: z.array(captureCapSchema).optional(),
153
- kind: captureDeviceKindSchema.optional(), // additive (0.4.0): engine-typed device family; absent on legacy producers
189
+ kind: captureDeviceKindSchema.optional(), // additive (0.4.0): engine-typed device family; absent on legacy producers. A dual-format device reports its HIGHEST-PRECEDENCE family here; the full set is `modes`
190
+ modes: z.array(captureModeSchema).optional(), // additive (0.11.0): every startable mode family, each with its OWN caps ladder; absent on legacy producers, on audio rows, and on a device with no probed caps. When present it ALWAYS contains the family named by `kind`, first
154
191
  alsa_card_id: z.string().optional(), // additive: ALSA card id for media_class:audio devices only; absent on video + legacy producers
155
192
  product_name: z.string().optional(), // additive (Todo 20): real product name, deduped with a #N suffix when shared; absent ⇒ use display_name
156
193
  transport: deviceTransportSchema.optional(), // additive (Todo 20): how the device is attached; absent on legacy producers
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ceralive/cerastream",
3
- "version": "2026.7.4",
3
+ "version": "2026.7.6",
4
4
  "description": "Type-safe TypeScript schema + IPC client for the cerastream streaming engine (JSON-RPC 2.0 / NDJSON over a Unix domain socket).",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",