@remavideo/sdk 0.16.2 → 0.17.0

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/index.d.ts CHANGED
@@ -1096,6 +1096,46 @@ interface VideoRenditionOptions {
1096
1096
  */
1097
1097
  label?: string;
1098
1098
  }
1099
+ /**
1100
+ * One rung of an HLS adaptive-bitrate (ABR) ladder — an independent
1101
+ * resolution/bitrate tier transcoded from the same video source, wired via
1102
+ * `VideoSubscriptionSpec.additionalRenditions` / `AllSubscriptionSpec.additionalRenditions`.
1103
+ * HLS-only; ignored by non-HLS output nodes.
1104
+ */
1105
+ interface VideoRendition {
1106
+ /**
1107
+ * Output height in pixels. Also derives the default label (`"{height}p"`)
1108
+ * and, when `width` is omitted, an aspect-preserving `scale=-2:height`
1109
+ * filter.
1110
+ */
1111
+ height: number;
1112
+ /** Output width in pixels. Omit to preserve the source aspect ratio. */
1113
+ width?: number;
1114
+ /** Target video bitrate — ffmpeg bitrate syntax (`"3M"`) or bits/sec. */
1115
+ bitrate: string | number;
1116
+ /** VBV maxrate. Default: same as `bitrate`. */
1117
+ maxrate?: string | number;
1118
+ /** VBV bufsize. Default: 2x `bitrate`. */
1119
+ bufsize?: string | number;
1120
+ /** libx264 preset. Default: `"veryfast"`. */
1121
+ preset?: string;
1122
+ /**
1123
+ * Encoder name override — escape hatch for hardware encoders (e.g.
1124
+ * `"h264_videotoolbox"`, `"h264_nvenc"`). Default: `"libx264"`. No
1125
+ * server-side hardware-encoder plumbing exists; passed through as-is.
1126
+ */
1127
+ codecName?: string;
1128
+ /**
1129
+ * Variant label. Default: `"{height}p"`. Must be unique across the
1130
+ * ladder — colliding labels are auto-suffixed server-side.
1131
+ */
1132
+ label?: string;
1133
+ /**
1134
+ * Extra raw codec options, merged in last — overrides any default above
1135
+ * (including the keyframe-alignment `force_key_frames` default).
1136
+ */
1137
+ extraOptions?: Record<string, string | number>;
1138
+ }
1099
1139
  /** Fields shared by every subscription variant. */
1100
1140
  interface SubscriptionSpecBase {
1101
1141
  source: BaseNode;
@@ -1131,6 +1171,18 @@ interface VideoSubscriptionSpec extends SubscriptionSpecBase {
1131
1171
  codec?: CodecSpec;
1132
1172
  /** Video rendition metadata (HLS). */
1133
1173
  video?: VideoRenditionOptions;
1174
+ /**
1175
+ * Additional ABR ladder tiers transcoded from this same source, each
1176
+ * becoming its own HLS variant alongside this subscription's own
1177
+ * rendition. HLS-only; ignored by non-HLS output nodes.
1178
+ */
1179
+ additionalRenditions?: VideoRendition[];
1180
+ /**
1181
+ * When true, this subscription's own rendition (this `codec`/`video`) is
1182
+ * NOT subscribed as a variant — only `additionalRenditions` are. HLS-only;
1183
+ * ignored by non-HLS output nodes.
1184
+ */
1185
+ omitOriginalRendition?: boolean;
1134
1186
  }
1135
1187
  /** Subscribe to an audio-only stream of a source node. */
1136
1188
  interface AudioSubscriptionSpec extends SubscriptionSpecBase {
@@ -1157,6 +1209,22 @@ interface AllSubscriptionSpec extends SubscriptionSpecBase {
1157
1209
  video?: VideoRenditionOptions;
1158
1210
  /** Audio rendition metadata (HLS). */
1159
1211
  audio?: AudioRenditionOptions;
1212
+ /**
1213
+ * Additional ABR ladder tiers transcoded from this same source, each
1214
+ * becoming its own video-only HLS variant alongside this subscription's
1215
+ * own combined A/V rendition. HLS-only; ignored by non-HLS output nodes.
1216
+ *
1217
+ * `omitOriginalRendition: true` on an `ALL` spec also drops this
1218
+ * subscription's own audio (since `ALL` bundles audio+video) — subscribe
1219
+ * audio separately (`sourceSelector: "AUDIO"`) if you still need it.
1220
+ */
1221
+ additionalRenditions?: VideoRendition[];
1222
+ /**
1223
+ * When true, this subscription's own combined rendition is NOT
1224
+ * subscribed — only `additionalRenditions` are. See the audio caveat
1225
+ * above. HLS-only; ignored by non-HLS output nodes.
1226
+ */
1227
+ omitOriginalRendition?: boolean;
1160
1228
  }
1161
1229
  /** Subscribe to the caption stream of a source node. */
1162
1230
  interface CaptionsSubscriptionSpec extends SubscriptionSpecBase {
@@ -1705,6 +1773,15 @@ declare class HlsOutputNode extends BaseNode {
1705
1773
  * per-session subfolder appended by `makePrefixUnique`.
1706
1774
  */
1707
1775
  effectivePaths(): Promise<string[]>;
1776
+ /**
1777
+ * Subscribe this node to one or more source nodes, expanding any
1778
+ * `VIDEO`/`ALL` spec's `additionalRenditions` into extra HLS variants
1779
+ * first (see `VideoRendition`). Keyframe alignment for generated
1780
+ * renditions uses this node's own `segmentTime`.
1781
+ */
1782
+ subscribe(specs: SubscriptionSpec[], options?: {
1783
+ gate?: ActivationGate;
1784
+ }): Promise<void>;
1708
1785
  }
1709
1786
 
1710
1787
  interface RtmpOutputSettings extends NodeCallbacks {
@@ -2199,4 +2276,4 @@ declare const selectAudio: "AUDIO";
2199
2276
  declare const selectCaptions: "CAPTIONS";
2200
2277
  declare const selectAll: "ALL";
2201
2278
 
2202
- export { type AllSubscriptionSpec, type AppRouter, type AudioRenditionOptions, type AudioSubscriptionSpec, type AzureSpeechLocale, type AzureTargetLanguageCode, AzureTranslateNode, type AzureTranslateSettings, type AzureVoiceName, BaseNode, BufferNode, type BufferSettings, CambAiTranslateNode, type CambAiTranslateSettings, type CaptionMode, CaptionSourceNode, type CaptionSourceSettings, type CaptionsSubscriptionSpec, Cea608EncoderNode, type Cea608EncoderSettings, type CodecSpec, FfprobeMonitorNode, type FfprobeMonitorSettings, FileInputNode, type FileInputSettings, FileOutputNode, type FileOutputSettings, GateNode, type GateSettings, GeminiTranslateNode, type GeminiTranslateSettings, type HealthStatus, type HlsDestination, HlsOutputNode, type HlsOutputSettings, type MonitorDescriptorMsg, type MonitorEventMsg, type NodeCallbacks, type NodeEventKind, type NodeEvents, type NodeNotificationKind, type NodeState, NodeStreamInputNode, type NodeStreamInputSettings, OpenAiTranslateNode, type OpenAiTranslateSettings, type OpenAiTranslateTargetLanguage, type PipelineSnapshot, Rema, type RemaClientOptions, RtmpAnnouncerNode, type RtmpAnnouncerSettings, type RtmpConnectionInfo, RtmpMultiplexerNode, type RtmpMultiplexerSettings, RtmpOutputNode, type RtmpOutputSettings, RtmpReaderNode, type RtmpReaderSettings, RtmpStreamNode, SrtAnnouncerNode, type SrtAnnouncerSettings, type SrtConnectionInfo, SrtOutputNode, type SrtOutputSettings, SrtReaderNode, type SrtReaderSettings, type StreamSelector, type SubscriptionSpec, type SystemResourceUsageMsg, type TrackInfo, type VideoRenditionOptions, type VideoSubscriptionSpec, type VisEdge, type VisNode, Workflow, type WorkflowCallbacks, type WorkflowEvents, type WorkflowInfo, type WorkflowResourceUsageMsg, createNodeStreamInputNode, selectAll, selectAudio, selectCaptions, selectVideo };
2279
+ export { type AllSubscriptionSpec, type AppRouter, type AudioRenditionOptions, type AudioSubscriptionSpec, type AzureSpeechLocale, type AzureTargetLanguageCode, AzureTranslateNode, type AzureTranslateSettings, type AzureVoiceName, BaseNode, BufferNode, type BufferSettings, CambAiTranslateNode, type CambAiTranslateSettings, type CaptionMode, CaptionSourceNode, type CaptionSourceSettings, type CaptionsSubscriptionSpec, Cea608EncoderNode, type Cea608EncoderSettings, type CodecSpec, FfprobeMonitorNode, type FfprobeMonitorSettings, FileInputNode, type FileInputSettings, FileOutputNode, type FileOutputSettings, GateNode, type GateSettings, GeminiTranslateNode, type GeminiTranslateSettings, type HealthStatus, type HlsDestination, HlsOutputNode, type HlsOutputSettings, type MonitorDescriptorMsg, type MonitorEventMsg, type NodeCallbacks, type NodeEventKind, type NodeEvents, type NodeNotificationKind, type NodeState, NodeStreamInputNode, type NodeStreamInputSettings, OpenAiTranslateNode, type OpenAiTranslateSettings, type OpenAiTranslateTargetLanguage, type PipelineSnapshot, Rema, type RemaClientOptions, RtmpAnnouncerNode, type RtmpAnnouncerSettings, type RtmpConnectionInfo, RtmpMultiplexerNode, type RtmpMultiplexerSettings, RtmpOutputNode, type RtmpOutputSettings, RtmpReaderNode, type RtmpReaderSettings, RtmpStreamNode, SrtAnnouncerNode, type SrtAnnouncerSettings, type SrtConnectionInfo, SrtOutputNode, type SrtOutputSettings, SrtReaderNode, type SrtReaderSettings, type StreamSelector, type SubscriptionSpec, type SystemResourceUsageMsg, type TrackInfo, type VideoRendition, type VideoRenditionOptions, type VideoSubscriptionSpec, type VisEdge, type VisNode, Workflow, type WorkflowCallbacks, type WorkflowEvents, type WorkflowInfo, type WorkflowResourceUsageMsg, createNodeStreamInputNode, selectAll, selectAudio, selectCaptions, selectVideo };
package/dist/index.js CHANGED
@@ -670,6 +670,82 @@ async function createFileOutputNode(workflow, settings, nodeId) {
670
670
 
671
671
  // src/output/hls.ts
672
672
  import { uid as uid9 } from "radash";
673
+
674
+ // src/output/hls-renditions.ts
675
+ var RENDITION_WARN_THRESHOLD = 3;
676
+ function bitsPerSecond(v) {
677
+ if (typeof v === "number") return v;
678
+ const match = /^([\d.]+)\s*([kKmM]?)$/.exec(v.trim());
679
+ if (!match) return Number(v) || 0;
680
+ const value = Number(match[1]);
681
+ const suffix = match[2]?.toLowerCase();
682
+ if (suffix === "k") return value * 1e3;
683
+ if (suffix === "m") return value * 1e6;
684
+ return value;
685
+ }
686
+ function buildRenditionSpec(source, track, r, segmentTime) {
687
+ const bitrate = String(r.bitrate);
688
+ const maxrate = r.maxrate !== void 0 ? String(r.maxrate) : bitrate;
689
+ const bufsize = r.bufsize !== void 0 ? String(r.bufsize) : String(2 * bitsPerSecond(r.bitrate));
690
+ return {
691
+ source,
692
+ sourceSelector: "VIDEO",
693
+ ...track !== void 0 ? { track } : {},
694
+ codec: {
695
+ name: r.codecName ?? "libx264",
696
+ options: {
697
+ preset: r.preset ?? "veryfast",
698
+ b: bitrate,
699
+ maxrate,
700
+ bufsize,
701
+ // fps-agnostic keyframe alignment so every rendition cuts HLS
702
+ // segments at the same wall-clock points — required for clean ABR
703
+ // switching (see VideoRendition docs / SDK docs CPU-cost section).
704
+ force_key_frames: `expr:gte(t,n_forced*${segmentTime})`,
705
+ // Per-stream options always get a `:v:N` specifier appended (see
706
+ // `buildCodecArgs`) — `filter` (not the bare `vf` alias, which does
707
+ // not accept a stream specifier: `-vf:v:N` is invalid FFmpeg syntax
708
+ // and fails with AVERROR(EINVAL)) becomes the valid `-filter:v:N`.
709
+ ...r.width !== void 0 ? { s: `${r.width}x${r.height}` } : { filter: `scale=-2:${r.height}` },
710
+ ...r.extraOptions
711
+ }
712
+ },
713
+ video: { label: r.label ?? `${r.height}p` }
714
+ };
715
+ }
716
+ function expandRenditionSpecs(specs, segmentTime) {
717
+ const result = [];
718
+ let videoVariantCount = 0;
719
+ for (const spec of specs) {
720
+ if (spec.sourceSelector !== "VIDEO" && spec.sourceSelector !== "ALL") {
721
+ result.push(spec);
722
+ continue;
723
+ }
724
+ const { additionalRenditions, omitOriginalRendition, ...rest } = spec;
725
+ if (!additionalRenditions || additionalRenditions.length === 0) {
726
+ result.push(spec);
727
+ videoVariantCount++;
728
+ continue;
729
+ }
730
+ if (!omitOriginalRendition) {
731
+ result.push(rest);
732
+ videoVariantCount++;
733
+ }
734
+ const track = spec.sourceSelector === "VIDEO" ? spec.track : void 0;
735
+ for (const r of additionalRenditions) {
736
+ result.push(buildRenditionSpec(spec.source, track, r, segmentTime));
737
+ videoVariantCount++;
738
+ }
739
+ }
740
+ if (videoVariantCount > RENDITION_WARN_THRESHOLD) {
741
+ console.warn(
742
+ `[rema] HLS output: ${videoVariantCount} video renditions means ${videoVariantCount} concurrent decode+encode passes inside one FFmpeg process \u2014 CPU cost scales ~linearly with rendition count, dominated by encode. Consider trimming tiers, a faster preset ("superfast"/"ultrafast"), or a hardware encoder via VideoRendition.codecName (e.g. "h264_videotoolbox", "h264_nvenc").`
743
+ );
744
+ }
745
+ return result;
746
+ }
747
+
748
+ // src/output/hls.ts
673
749
  var HlsOutputNode = class extends BaseNode {
674
750
  settings;
675
751
  constructor(nodeId, workflow, settings) {
@@ -689,6 +765,18 @@ var HlsOutputNode = class extends BaseNode {
689
765
  });
690
766
  return result.effectivePaths;
691
767
  }
768
+ /**
769
+ * Subscribe this node to one or more source nodes, expanding any
770
+ * `VIDEO`/`ALL` spec's `additionalRenditions` into extra HLS variants
771
+ * first (see `VideoRendition`). Keyframe alignment for generated
772
+ * renditions uses this node's own `segmentTime`.
773
+ */
774
+ async subscribe(specs, options) {
775
+ await super.subscribe(
776
+ expandRenditionSpecs(specs, this.settings.segmentTime ?? 6),
777
+ options
778
+ );
779
+ }
692
780
  };
693
781
  async function createHlsOutputNode(workflow, settings, nodeId) {
694
782
  const id = nodeId ?? `hls_output_${uid9(12)}`;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/input/caption-source.ts","../src/node.ts","../src/input/file.ts","../src/input/node-stream.ts","../src/input/rtmp-announcer.ts","../src/input/rtmp-multiplexer.ts","../src/input/rtmp-reader.ts","../src/input/srt-announcer.ts","../src/input/srt-reader.ts","../src/output/file.ts","../src/output/hls.ts","../src/output/rtmp.ts","../src/output/srt.ts","../src/processor/azure-translate.ts","../src/processor/buffer.ts","../src/processor/camb-ai-translate.ts","../src/processor/cea608-encoder.ts","../src/processor/ffprobe-monitor.ts","../src/processor/gate.ts","../src/processor/gemini-translate.ts","../src/processor/openai-translate.ts","../src/client.ts","../src/workflow.ts","../src/processor/audio-mixer.ts","../src/rema.ts","../src/selectors.ts"],"sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport interface CaptionSourceSettings extends NodeCallbacks {\n /**\n * How many past caption entries to replay to an encoder that subscribes\n * after some captions have already been sent.\n *\n * - `0` (default) — no replay; each encoder only receives captions sent\n * after it connects. Correct for live captioning where old text is stale.\n * - `Infinity` — replay the full history. Use this when pre-scheduling\n * all captions upfront (e.g. from an SRT file) before the pipeline starts,\n * so encoders that activate later still see every entry.\n * - `N` — replay the last N entries only.\n */\n replayCount?: number;\n /**\n * BCP-47 language tag written into the HLS master playlist when this caption\n * source is wired as a subtitle track. Default: \"und\".\n */\n language?: string;\n /** Human-readable track label shown in player subtitle menus. Default: \"Subtitles\". */\n label?: string;\n /** Whether this track is pre-selected when the player first loads. Default: true. */\n defaultTrack?: boolean;\n /**\n * Maximum character length per caption line. When set, captions whose text\n * exceeds this length are automatically split into sequential chunks on the\n * server. Each chunk's display duration is proportional to its character\n * count relative to the original caption's total duration.\n */\n maxLineLength?: number;\n}\n\nexport class CaptionSourceNode extends BaseNode {\n readonly settings: CaptionSourceSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: CaptionSourceSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n\n /**\n * Queue caption text for injection into the video stream.\n *\n * @param options.startAt Stream-elapsed milliseconds (relative to the\n * first video frame received by the server) at which\n * to show the caption. The server holds the text and\n * injects it at the matching PTS, so you can schedule\n * captions before that point in the stream is reached.\n * Omit (or 0) to inject immediately at the next frame.\n * @param options.duration How long (ms) the caption stays visible before\n * being automatically erased. Required.\n */\n async send(\n text: string,\n options: { duration: number; startAt?: number },\n ): Promise<void> {\n await this.client.trpc.sendCaption.mutate({\n workflowId: this.workflowId,\n nodeId: this.nodeId,\n text,\n durationMs: options.duration,\n startAtMs: options.startAt ?? 0,\n });\n }\n\n /**\n * Parse raw SRT or WebVTT content and schedule all entries on the server.\n *\n * Use `replayCount: Infinity` when creating this node if the pipeline may\n * connect encoders after the captions are loaded.\n *\n * @returns The number of caption entries loaded.\n */\n async sendFile(content: string): Promise<{ count: number }> {\n return this.client.trpc.loadCaptionFile.mutate({\n workflowId: this.workflowId,\n nodeId: this.nodeId,\n content,\n });\n }\n\n /**\n * Read a local SRT or WebVTT file and schedule all its entries on the server.\n * The file is read client-side; only the content string is sent over the wire.\n *\n * @param filePath Absolute or relative path to the subtitle file.\n * @returns The number of caption entries loaded.\n */\n async sendFileByPath(filePath: string): Promise<{ count: number }> {\n const content = await readFile(filePath, \"utf-8\");\n return this.sendFile(content);\n }\n}\n\nexport async function createCaptionSourceNode(\n workflow: WorkflowHandle,\n settings: CaptionSourceSettings = {},\n nodeId?: string,\n): Promise<CaptionSourceNode> {\n const id = nodeId ?? `caption_source_${Date.now()}`;\n const node = new CaptionSourceNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"CAPTION_SOURCE\",\n workflowId: workflow.workflowId,\n nodeId: id,\n captionSource: {\n replayCount: settings.replayCount ?? 0,\n language: settings.language ?? \"und\",\n label: settings.label ?? \"Subtitles\",\n defaultTrack: settings.defaultTrack ?? true,\n ...(settings.maxLineLength !== undefined && {\n maxLineLength: settings.maxLineLength,\n }),\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import type {\n ActivationGate,\n NodeEventMsg,\n NodeState,\n TrackInfo,\n} from \"@remavideo/server\";\nimport { TypedEmitter } from \"tiny-typed-emitter\";\nimport type { RemaClient } from \"./client.js\";\n\n/** Lifecycle callbacks available on every node's settings object. */\nexport interface NodeCallbacks {\n /** Called when the node transitions into the `ERROR` state. */\n onError?: (err: Error) => void;\n /** Called on every lifecycle state transition (`READY`, `ACTIVE`, `ENDED`, `ERROR`, `CLOSED`). */\n onStatusChange?: (state: NodeState, message?: string) => void;\n /** Called when the node closes — locally via `.close()`, or remotely (workflow close, crash). */\n onClose?: () => void;\n}\n\n/** Events emitted by every node, mirroring {@link NodeCallbacks} for `.on()`-style consumers. */\nexport interface NodeEvents {\n error: (err: Error) => void;\n statusChange: (state: NodeState, message?: string) => void;\n close: () => void;\n}\n\nconst NODE_STATES = new Set<NodeState>([\n \"READY\",\n \"ACTIVE\",\n \"ENDED\",\n \"ERROR\",\n \"CLOSED\",\n]);\n\n/**\n * Report an error to a node's `onError` callback and its `NodeEvents`\n * emitter. The SDK does no logging of its own — the caller decides whether\n * and how an unhandled error surfaces, via `onError` or `.on(\"error\", ...)`.\n *\n * `BaseNode` extends Node's `EventEmitter` (via `tiny-typed-emitter`), which\n * throws synchronously from `emit()` when an `\"error\"` event has no\n * listener. Since callers typically only set the `onError` callback (not\n * `.on(\"error\", ...)`), emitting unconditionally would throw back into\n * whatever triggered this call — e.g. the `workflow.events` subscription's\n * `onData` handler, tearing down event delivery for the entire workflow.\n * Only emit when someone has opted in.\n */\nexport function reportNodeError(\n node: BaseNode,\n callbacks: NodeCallbacks,\n err: Error,\n): void {\n callbacks.onError?.(err);\n if (node.listenerCount(\"error\") > 0) node.emit(\"error\", err);\n}\n\n/**\n * Dispatch a raw node event to the generic lifecycle callbacks on a node's\n * settings object, and to the node's `NodeEvents` emitter. Call this from a\n * node factory's event handler alongside any node-type-specific event handling.\n */\nexport function dispatchNodeCallbacks(\n node: BaseNode,\n evt: NodeEventMsg,\n callbacks: NodeCallbacks,\n): void {\n if (NODE_STATES.has(evt.kind as NodeState)) {\n const state = evt.kind as NodeState;\n callbacks.onStatusChange?.(state, evt.message);\n node.emit(\"statusChange\", state, evt.message);\n }\n if (evt.kind === \"ERROR\") {\n reportNodeError(node, callbacks, new Error(evt.message ?? \"node error\"));\n }\n if (evt.kind === \"CLOSED\") {\n callbacks.onClose?.();\n node.emit(\"close\");\n }\n}\n\n/**\n * FFmpeg codec specification for one stream of a subscription.\n *\n * The receiving output node applies it where appropriate (e.g. per-rendition\n * `-c:v:N`/`-c:a:N` for HLS, `-c:v`/`-c:a` for RTMP/File outputs). Omitted =\n * stream copy.\n */\nexport interface CodecSpec {\n /** FFmpeg encoder name: `\"copy\"`, `\"aac\"`, `\"libx264\"`, … */\n name: string;\n /**\n * Extra encoder options keyed by bare FFmpeg option name (no stream\n * specifier), e.g. `{ preset: \"veryfast\", b: \"3M\" }`.\n */\n options?: Record<string, string | number>;\n}\n\n/**\n * Audio rendition metadata for HLS output nodes. Sets the language tag and\n * label that appear in the player's track menu. Ignored by non-HLS nodes.\n */\nexport interface AudioRenditionOptions {\n /** BCP-47 language tag, e.g. `\"en\"`, `\"fr\"`. Default: `\"und\"`. */\n language?: string;\n /** Label shown in player track menus. Default: `\"Audio\"`. */\n label?: string;\n /** Pre-select this track on first play. Default: `false`. */\n defaultTrack?: boolean;\n}\n\n/**\n * Video rendition metadata for HLS output nodes. Ignored by non-HLS nodes.\n */\nexport interface VideoRenditionOptions {\n /**\n * Label used to name the HLS variant playlist/segments (sanitized to a\n * URL-safe variant name).\n */\n label?: string;\n}\n\n/** Fields shared by every subscription variant. */\ninterface SubscriptionSpecBase {\n source: BaseNode;\n /**\n * Delay this stream by N milliseconds before it reaches the subscribing\n * node (applied server-side). Typical use: delay the A/V subscriptions so\n * a slower caption source has lead time.\n */\n delayMs?: number;\n /**\n * Read from a keyed output port of a multi-output source node (Gate).\n * Ports are named by the `name` field of the specs the gate itself was\n * subscribed with. Mutually exclusive with `track`.\n */\n port?: string;\n /**\n * When the *subscribing* node is a multi-output node (Gate), names the\n * pass-through port it creates for this source (default: the source's\n * index as a string). Ignored by other node types.\n */\n name?: string;\n}\n\n/** Subscribe to a video-only stream of a source node. */\nexport interface VideoSubscriptionSpec extends SubscriptionSpecBase {\n sourceSelector: \"VIDEO\";\n /**\n * Per-type video track index within the source container (0 = first video\n * track). Discover available indices with `await source.tracks()`.\n * Default: 0.\n */\n track?: number;\n /** Codec applied to the video stream by the receiving output node. */\n codec?: CodecSpec;\n /** Video rendition metadata (HLS). */\n video?: VideoRenditionOptions;\n}\n\n/** Subscribe to an audio-only stream of a source node. */\nexport interface AudioSubscriptionSpec extends SubscriptionSpecBase {\n sourceSelector: \"AUDIO\";\n /**\n * Per-type audio track index within the source container (0 = first audio\n * track). Discover available indices with `await source.tracks()`.\n * Default: 0.\n */\n track?: number;\n /** Codec applied to the audio stream by the receiving output node. */\n codec?: CodecSpec;\n /** Audio rendition metadata (HLS). */\n audio?: AudioRenditionOptions;\n}\n\n/** Subscribe to the combined A/V stream of a source node. */\nexport interface AllSubscriptionSpec extends SubscriptionSpecBase {\n sourceSelector: \"ALL\";\n /** Codec applied to the video track by the receiving output node. */\n videoCodec?: CodecSpec;\n /** Codec applied to the audio track by the receiving output node. */\n audioCodec?: CodecSpec;\n /** Video rendition metadata (HLS). */\n video?: VideoRenditionOptions;\n /** Audio rendition metadata (HLS). */\n audio?: AudioRenditionOptions;\n}\n\n/** Subscribe to the caption stream of a source node. */\nexport interface CaptionsSubscriptionSpec extends SubscriptionSpecBase {\n sourceSelector: \"CAPTIONS\";\n}\n\n/**\n * One source wired into a subscribing node, discriminated by\n * `sourceSelector` so per-stream options (codec, rendition metadata) are\n * only available where they apply.\n */\nexport type SubscriptionSpec =\n | VideoSubscriptionSpec\n | AudioSubscriptionSpec\n | AllSubscriptionSpec\n | CaptionsSubscriptionSpec;\n\n/**\n * Minimal interface passed to factory functions. Defined here (not in workflow.ts)\n * to avoid a circular import: workflow.ts imports factory files, factory files\n * import this interface.\n */\nexport interface WorkflowHandle {\n readonly workflowId: string;\n readonly client: RemaClient;\n _onNodeEvent(\n nodeId: string,\n handler: (evt: NodeEventMsg) => void,\n ): () => void;\n}\n\n/**\n * Base class for all rema nodes.\n * Provides the .subscribe() method that wires nodes together on the server,\n * and a `NodeEvents` emitter (`.on(\"error\"/\"statusChange\"/\"close\", ...)`) as\n * an alternative to the `onError`/`onStatusChange`/`onClose` settings callbacks.\n */\nexport abstract class BaseNode extends TypedEmitter<NodeEvents> {\n readonly nodeId: string;\n readonly workflowId: string;\n protected readonly client: RemaClient;\n private _eventCleanup: (() => void) | null = null;\n private _callbacks: NodeCallbacks = {};\n\n constructor(nodeId: string, client: RemaClient, workflowId: string) {\n super();\n this.nodeId = nodeId;\n this.workflowId = workflowId;\n this.client = client;\n }\n\n /** Called by create functions to register the workflow event handler cleanup. */\n _setEventCleanup(fn: () => void): void {\n this._eventCleanup = fn;\n }\n\n /**\n * Called by create functions to register the node's lifecycle callbacks\n * (`onError`/`onStatusChange`/`onClose`), so `.close()` can invoke `onClose`\n * directly on a locally-initiated close.\n */\n _bindCallbacks(callbacks: NodeCallbacks): void {\n this._callbacks = callbacks;\n }\n\n /**\n * Subscribe this node to one or more source nodes.\n * This tells the server to route streams from the sources into this node.\n */\n async subscribe(\n specs: SubscriptionSpec[],\n options?: { gate?: ActivationGate },\n ): Promise<void> {\n await this.client.trpc.subscribeNode.mutate({\n workflowId: this.workflowId,\n nodeId: this.nodeId,\n sources: specs.map((s) => BaseNode._toSourceInput(s)),\n gate: options?.gate,\n });\n }\n\n /** Map one `SubscriptionSpec` to the wire shape expected by the server. */\n private static _toSourceInput(spec: SubscriptionSpec) {\n const base = {\n sourceNodeId: spec.source.nodeId,\n ...(spec.delayMs !== undefined ? { delayMs: spec.delayMs } : {}),\n ...(spec.port !== undefined ? { port: spec.port } : {}),\n ...(spec.name !== undefined ? { name: spec.name } : {}),\n };\n const audio = (a: AudioRenditionOptions) => ({\n language: a.language ?? \"und\",\n label: a.label ?? \"Audio\",\n defaultTrack: a.defaultTrack ?? false,\n });\n // `video` is only sent when a label is provided — it is the only field\n // and is required on the wire.\n const video = (v: VideoRenditionOptions) =>\n v.label !== undefined ? { video: { label: v.label } } : {};\n\n switch (spec.sourceSelector) {\n case \"CAPTIONS\":\n return { ...base, selector: \"CAPTIONS\" as const };\n case \"VIDEO\":\n return {\n ...base,\n selector: \"VIDEO\" as const,\n ...(spec.track !== undefined ? { track: spec.track } : {}),\n ...(spec.codec !== undefined ? { codec: spec.codec } : {}),\n ...(spec.video !== undefined ? video(spec.video) : {}),\n };\n case \"AUDIO\":\n return {\n ...base,\n selector: \"AUDIO\" as const,\n ...(spec.track !== undefined ? { track: spec.track } : {}),\n ...(spec.codec !== undefined ? { codec: spec.codec } : {}),\n ...(spec.audio !== undefined ? { audio: audio(spec.audio) } : {}),\n };\n case \"ALL\":\n return {\n ...base,\n selector: \"ALL\" as const,\n ...(spec.videoCodec !== undefined\n ? { videoCodec: spec.videoCodec }\n : {}),\n ...(spec.audioCodec !== undefined\n ? { audioCodec: spec.audioCodec }\n : {}),\n ...(spec.video !== undefined ? video(spec.video) : {}),\n ...(spec.audio !== undefined ? { audio: audio(spec.audio) } : {}),\n };\n }\n }\n\n /**\n * Probe this node's media stream (ffprobe, server-side) and return its\n * track list. Each entry's `track` is the per-type index to pass back as\n * `track` in a VIDEO/AUDIO subscription:\n *\n * ```ts\n * const tracks = await input.tracks();\n * const ita = tracks.find((t) => t.type === \"audio\" && t.language === \"ita\");\n * await output.subscribe([\n * { source: input, sourceSelector: \"VIDEO\" },\n * { source: input, sourceSelector: \"AUDIO\", track: ita!.track },\n * ]);\n * ```\n *\n * Probing consumes real-time-paced stream data, so it can take several\n * seconds; `probeSizeBytes` (default 1 MB) trades discovery depth for\n * latency. The server errors after ~15 s if the source produces no data\n * (e.g. no publisher connected yet, or a disabled file input).\n */\n async tracks(options?: { probeSizeBytes?: number }): Promise<TrackInfo[]> {\n return this.client.trpc.probeTracks.query({\n workflowId: this.workflowId,\n nodeId: this.nodeId,\n ...(options?.probeSizeBytes !== undefined\n ? { probeSizeBytes: options.probeSizeBytes }\n : {}),\n });\n }\n\n /** Tear down this node on the server. */\n async close(): Promise<void> {\n this._eventCleanup?.();\n await this.client.trpc.closeNode.mutate({\n workflowId: this.workflowId,\n nodeId: this.nodeId,\n });\n this._callbacks.onClose?.();\n this.emit(\"close\");\n }\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport interface FileInputSettings extends NodeCallbacks {\n /** Absolute path to the media file (MP4, TS, MKV, …) */\n fileName: string;\n /** Loop the file indefinitely. Default: false */\n loop?: boolean;\n /** Source name used to identify streams from this node */\n sourceName?: string;\n /**\n * Start in a disabled state — the file is not read until `.enable()` is called\n * (or the node is enabled from the web UI). Default: false.\n */\n disabled?: boolean;\n /** Called when the node transitions to the enabled / ready state */\n onReady?: () => void;\n /**\n * Called once when the first data packet from this input arrives at the server.\n * Use this to convert absolute timestamps to the stream-relative `startAt`\n * expected by `CaptionSourceNode.send()`:\n *\n * ```ts\n * onStreamStart(wallClockMs) {\n * const startAt = myAbsoluteCaptionMs - wallClockMs;\n * await captions.send(text, { startAt });\n * }\n * ```\n *\n * @param wallClockMs `Date.now()` on the server at the moment the first\n * MPEG-TS data packet arrived from this input.\n */\n onStreamStart?: (wallClockMs: number) => void;\n /** Called once the file has finished playing (reaches EOF). Never fires when `loop` is true. */\n onEnded?: () => void;\n}\n\nexport class FileInputNode extends BaseNode {\n readonly settings: FileInputSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: FileInputSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n\n /** Enable this node — starts reading the file and streaming to downstream nodes. */\n async enable(): Promise<void> {\n await this.client.trpc.enableNode.mutate({\n workflowId: this.workflowId,\n nodeId: this.nodeId,\n });\n }\n}\n\nexport async function createFileInputNode(\n workflow: WorkflowHandle,\n settings: FileInputSettings,\n nodeId?: string,\n): Promise<FileInputNode> {\n const id = nodeId ?? `file_input_${uid(12)}`;\n const node = new FileInputNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n if (evt.kind === \"READY\") settings.onReady?.();\n if (evt.kind === \"ACTIVE\" && evt.message !== undefined)\n settings.onStreamStart?.(Number(evt.message));\n if (evt.kind === \"ENDED\") settings.onEnded?.();\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"FILE_INPUT\",\n workflowId: workflow.workflowId,\n nodeId: id,\n fileInput: {\n fileName: settings.fileName,\n loop: settings.loop ?? false,\n sourceName: settings.sourceName,\n disabled: settings.disabled ?? false,\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport interface NodeStreamInputSettings extends NodeCallbacks {\n /**\n * FFmpeg format identifier for the incoming audio data.\n *\n * Raw PCM examples: `\"s16le\"` (16-bit signed LE), `\"f32le\"` (32-bit float LE).\n * Encoded examples: `\"wav\"`, `\"mp3\"`, `\"aac\"`, `\"flac\"`.\n */\n format: string;\n /** Sample rate in Hz. Required when `format` is a raw PCM format. */\n sampleRate?: number;\n /** Number of audio channels. Required when `format` is a raw PCM format. */\n channels?: number;\n /**\n * FFmpeg codec for the MPEG-TS output. Defaults to `\"aac\"`, which is safe\n * for any input format. Use `\"copy\"` only when the input is already\n * MPEG-TS-compatible AAC.\n */\n audioCodec?: string;\n /** Human-readable label shown in the dashboard. */\n label?: string;\n /** Called when the node transitions to the ready state on the server. */\n onReady?: () => void;\n /**\n * Called once when the first audio packet is written to the server.\n * The argument is `Date.now()` on the server at that moment.\n */\n onStreamStart?: (wallClockMs: number) => void;\n /** Called once the underlying FFmpeg process exits cleanly after `end()`. */\n onEnded?: () => void;\n}\n\nexport class NodeStreamInputNode extends BaseNode {\n readonly settings: NodeStreamInputSettings;\n private readonly _apiBase: string;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: NodeStreamInputSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n const { host, port } = workflow.client.options;\n this._apiBase = `http://${host}:${port}`;\n }\n\n /**\n * Push an audio stream to the server.\n *\n * Accepts any `AsyncIterable<Buffer | Uint8Array>`, which includes Node.js\n * `Readable` streams (async-iterable since Node 10) and async generators.\n * The call resolves when the source iterable is exhausted or the server\n * closes the connection.\n *\n * @example\n * ```ts\n * import { createReadStream } from \"node:fs\";\n * await audioInput.stream(createReadStream(\"audio.mp3\"));\n * ```\n *\n * @example\n * ```ts\n * // Raw PCM from a microphone via node-record-lpcm16\n * const mic = recorder.start({ sampleRate: 16000, channels: 1 });\n * await audioInput.stream(mic);\n * ```\n */\n async stream(source: AsyncIterable<Buffer | Uint8Array>): Promise<void> {\n const url = `${this._apiBase}/api/node-stream/${this.workflowId}/${this.nodeId}`;\n\n const body = new ReadableStream<Uint8Array>({\n async start(controller) {\n try {\n for await (const chunk of source) {\n controller.enqueue(\n chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk),\n );\n }\n controller.close();\n } catch (err) {\n controller.error(err);\n }\n },\n });\n\n await fetch(url, {\n method: \"POST\",\n body,\n // Required for streaming request bodies in Node.js 18+ native fetch.\n duplex: \"half\",\n } as RequestInit);\n }\n}\n\nexport async function createNodeStreamInputNode(\n workflow: WorkflowHandle,\n settings: NodeStreamInputSettings,\n nodeId?: string,\n): Promise<NodeStreamInputNode> {\n const id = nodeId ?? `node_stream_${uid(12)}`;\n const node = new NodeStreamInputNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n if (evt.kind === \"READY\") settings.onReady?.();\n if (evt.kind === \"ACTIVE\" && evt.message !== undefined)\n settings.onStreamStart?.(Number(evt.message));\n if (evt.kind === \"ENDED\") settings.onEnded?.();\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"NODE_STREAM_INPUT\",\n workflowId: workflow.workflowId,\n nodeId: id,\n nodeStreamInput: {\n format: settings.format,\n ...(settings.sampleRate !== undefined && {\n sampleRate: settings.sampleRate,\n }),\n ...(settings.channels !== undefined && {\n channels: settings.channels,\n }),\n audioCodec: settings.audioCodec ?? \"aac\",\n ...(settings.label !== undefined && { label: settings.label }),\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\n/** Connection info passed to `onConnect` and `onDisconnect`. */\nexport interface RtmpConnectionInfo {\n app: string;\n sourceName: string;\n /**\n * MediaMTX connection id for this publisher, if it could be resolved at\n * connect time. Pass directly to `operations.disconnectSource`.\n */\n connectionId?: string;\n}\n\n/** Operations available on a live RTMP publisher connection. */\nexport interface RtmpAnnouncerOperations {\n /**\n * Forcibly disconnect the connection identified by `connectionId`. Safe to\n * call even if it already disconnected (resolves as a no-op).\n */\n disconnectSource(connectionId: string): Promise<void>;\n}\n\nexport interface RtmpAnnouncerSettings extends NodeCallbacks {\n /** RTMP app name to watch (default: \"live\"). */\n app?: string;\n /**\n * Called when a publisher connects. Create nodes in any workflow from\n * here, or call `operations.disconnectSource(info.connectionId)` to kick\n * the publisher synchronously (e.g. to reject an invalid `sourceName`).\n */\n onConnect: (\n info: RtmpConnectionInfo,\n operations: RtmpAnnouncerOperations,\n ) => void;\n /** Called when a publisher disconnects. */\n onDisconnect?: (info: RtmpConnectionInfo) => void;\n}\n\n/** SDK handle for an RTMP announcer node. */\nexport class RtmpAnnouncerNode extends BaseNode {\n /**\n * Forcibly disconnect the connection identified by `connectionId`. Can be\n * called at any time after the node is created — e.g. from an async\n * moderation decision made after `onConnect` already returned.\n */\n async disconnectSource(connectionId: string): Promise<void> {\n await this.client.trpc.disconnectSource.mutate({\n workflowId: this.workflowId,\n nodeId: this.nodeId,\n connectionId,\n });\n }\n}\n\nexport async function createRtmpAnnouncerNode(\n workflow: WorkflowHandle,\n settings: RtmpAnnouncerSettings,\n nodeId?: string,\n): Promise<RtmpAnnouncerNode> {\n const id = nodeId ?? `rtmp_announcer_${uid(12)}`;\n const app = settings.app ?? \"live\";\n const node = new RtmpAnnouncerNode(id, workflow.client, workflow.workflowId);\n const operations: RtmpAnnouncerOperations = {\n disconnectSource: (connectionId) => node.disconnectSource(connectionId),\n };\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n if (evt.kind === \"STREAM_CONNECTED\" && evt.message !== undefined) {\n const info = JSON.parse(evt.message) as RtmpConnectionInfo;\n settings.onConnect(info, operations);\n }\n if (evt.kind === \"STREAM_DISCONNECTED\" && evt.message !== undefined) {\n const info = JSON.parse(evt.message) as RtmpConnectionInfo;\n settings.onDisconnect?.(info);\n }\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"RTMP_ANNOUNCER\",\n workflowId: workflow.workflowId,\n nodeId: id,\n rtmpAnnouncer: { app },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport type { RemaClient } from \"../client.js\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n reportNodeError,\n type WorkflowHandle,\n} from \"../node.js\";\n\n/** Normalize a `catch`-clause value (which TypeScript types as `unknown`) into an `Error`. */\nfunction toError(err: unknown): Error {\n return err instanceof Error ? err : new Error(String(err));\n}\n\n// ── RtmpStreamNode ─────────────────────────────────────────────────────────────\n\n/**\n * Represents a single active RTMP stream created by an `RtmpMultiplexerNode`.\n *\n * The server creates this node automatically when a publisher connects. The SDK\n * wraps the server-side node ID so it can be passed as a source to downstream\n * nodes (encoders, outputs) via `subscribe()`.\n */\nexport class RtmpStreamNode extends BaseNode {\n readonly app: string;\n readonly sourceName: string;\n\n constructor(\n nodeId: string,\n client: RemaClient,\n app: string,\n sourceName: string,\n workflowId: string,\n ) {\n super(nodeId, client, workflowId);\n this.app = app;\n this.sourceName = sourceName;\n }\n}\n\n// ── RtmpMultiplexerNode ────────────────────────────────────────────────────────\n\n/**\n * SDK handle for an RTMP multiplexer node.\n *\n * Calling `close()` deactivates the multiplexer and all of its active stream\n * nodes on the server.\n */\nexport class RtmpMultiplexerNode extends BaseNode {}\n\n// ── Settings ──────────────────────────────────────────────────────────────────\n\nexport interface RtmpMultiplexerSettings extends NodeCallbacks {\n /** RTMP app name to listen on (default: \"live\"). */\n app?: string;\n\n /**\n * Called when a publisher connects. Return `{ accept: false }` to kick the\n * connection immediately; return `{ accept: true }` or omit the return value\n * to accept and trigger `onStream`.\n */\n onConnect?: (info: {\n app: string;\n sourceName: string;\n }) =>\n | Promise<{ accept: boolean } | undefined>\n | { accept: boolean }\n | undefined;\n\n /**\n * Called with the per-publisher `RtmpStreamNode` when a new RTMP stream is\n * accepted. Create your downstream pipeline (encoders, outputs) from this node.\n */\n onStream: (node: RtmpStreamNode) => Promise<void> | void;\n\n /**\n * Called when a publisher disconnects or is rejected. Use this to close any\n * downstream nodes you created in `onStream`.\n */\n onStreamEnd?: (node: RtmpStreamNode) => Promise<void> | void;\n}\n\n// ── Factory ───────────────────────────────────────────────────────────────────\n\nexport async function createRtmpMultiplexerNode(\n workflow: WorkflowHandle,\n settings: RtmpMultiplexerSettings,\n nodeId?: string,\n): Promise<RtmpMultiplexerNode> {\n const id = nodeId ?? `rtmp_mux_${uid(12)}`;\n const app = settings.app ?? \"live\";\n const node = new RtmpMultiplexerNode(\n id,\n workflow.client,\n workflow.workflowId,\n );\n\n // Track active stream nodes by sourceName for onStreamEnd lookup.\n const activeStreams = new Map<string, RtmpStreamNode>();\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n if (evt.kind === \"CHILD_CREATED\" && evt.message !== undefined) {\n const { childNodeId, sourceName } = JSON.parse(evt.message) as {\n childNodeId: string;\n sourceName: string;\n };\n\n Promise.resolve(settings.onConnect?.({ app, sourceName }))\n .then(async (result) => {\n if (result?.accept === false) {\n await workflow.client.trpc.rejectPublisher\n .mutate({\n workflowId: workflow.workflowId,\n nodeId: id,\n sourceName,\n })\n .catch((err: unknown) => {\n reportNodeError(node, settings, toError(err));\n });\n return;\n }\n const streamNode = new RtmpStreamNode(\n childNodeId,\n workflow.client,\n app,\n sourceName,\n workflow.workflowId,\n );\n activeStreams.set(sourceName, streamNode);\n await settings.onStream(streamNode);\n })\n .catch((err: unknown) => {\n reportNodeError(node, settings, toError(err));\n });\n return;\n }\n\n if (evt.kind === \"CHILD_CLOSED\" && evt.message !== undefined) {\n const { sourceName } = JSON.parse(evt.message) as { sourceName: string };\n const streamNode = activeStreams.get(sourceName);\n activeStreams.delete(sourceName);\n if (streamNode) {\n Promise.resolve(settings.onStreamEnd?.(streamNode)).catch(\n (err: unknown) => {\n reportNodeError(node, settings, toError(err));\n },\n );\n }\n }\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"RTMP_MULTIPLEXER\",\n workflowId: workflow.workflowId,\n nodeId: id,\n rtmpMultiplexer: { app, port: 1935 },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport interface RtmpReaderSettings extends NodeCallbacks {\n /** RTMP app name. */\n app: string;\n /** Source name (stream key). */\n sourceName: string;\n /**\n * Retry policy for a failed FFmpeg pull from the internal MediaMTX relay\n * (e.g. a transient network blip). All fields optional; defaults to 5\n * attempts with 1s→10s exponential backoff before the node errors out.\n */\n retry?: {\n /** Max retries before giving up and marking the node ERROR. Default: 5. */\n maxAttempts?: number;\n /** Initial backoff delay in ms before the first retry. Default: 1000. */\n baseDelayMs?: number;\n /** Backoff delay ceiling in ms. Default: 10000. */\n maxDelayMs?: number;\n };\n /** Called when an RTMP publisher connects. */\n onPublish?: (info: { app: string; internalUrl: string }) => void;\n /**\n * Called once when the first data packet from this input arrives at the server.\n * @param wallClockMs `Date.now()` on the server at the moment the first MPEG-TS packet arrived.\n */\n onStreamStart?: (wallClockMs: number) => void;\n /** Called when the publisher disconnects (the underlying FFmpeg pull exits). */\n onStreamEnded?: () => void;\n}\n\nexport class RtmpReaderNode extends BaseNode {\n readonly settings: RtmpReaderSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: RtmpReaderSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n}\n\nexport async function createRtmpReaderNode(\n workflow: WorkflowHandle,\n settings: RtmpReaderSettings,\n nodeId?: string,\n): Promise<RtmpReaderNode> {\n const id = nodeId ?? `rtmp_reader_${uid(12)}`;\n const node = new RtmpReaderNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n if (evt.kind === \"READY\") {\n settings.onPublish?.({\n app: settings.app,\n internalUrl: evt.message ?? \"\",\n });\n }\n if (evt.kind === \"ACTIVE\" && evt.message !== undefined) {\n settings.onStreamStart?.(Number(evt.message));\n }\n if (evt.kind === \"ENDED\") settings.onStreamEnded?.();\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"RTMP_READER\",\n workflowId: workflow.workflowId,\n nodeId: id,\n rtmpReader: {\n app: settings.app,\n sourceName: settings.sourceName,\n ...(settings.retry !== undefined && { retry: settings.retry }),\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\n/** Connection info passed to `onConnect` and `onDisconnect`. */\nexport interface SrtConnectionInfo {\n app: string;\n sourceName: string;\n /** Passphrase configured on the announcer, if any. */\n passphrase?: string;\n /** Negotiated SRT latency in milliseconds, if configured. */\n latencyMs?: number;\n /**\n * MediaMTX connection id for this publisher, if it could be resolved at\n * connect time. Pass directly to `operations.disconnectSource`.\n */\n connectionId?: string;\n}\n\n/** Operations available on a live SRT publisher connection. */\nexport interface SrtAnnouncerOperations {\n /**\n * Forcibly disconnect the connection identified by `connectionId`. Safe to\n * call even if it already disconnected (resolves as a no-op).\n */\n disconnectSource(connectionId: string): Promise<void>;\n}\n\nexport interface SrtAnnouncerSettings extends NodeCallbacks {\n /** SRT app name to watch (default: \"live\"). */\n app?: string;\n /** Passphrase to include in connection events (mirrors MediaMTX path config). */\n passphrase?: string;\n /** SRT latency in milliseconds to include in connection events. */\n latencyMs?: number;\n /**\n * Called when a publisher connects. Create nodes in any workflow from\n * here, or call `operations.disconnectSource(info.connectionId)` to kick\n * the publisher synchronously (e.g. to reject an invalid `sourceName`).\n */\n onConnect: (\n info: SrtConnectionInfo,\n operations: SrtAnnouncerOperations,\n ) => void;\n /** Called when a publisher disconnects. */\n onDisconnect?: (info: Pick<SrtConnectionInfo, \"app\" | \"sourceName\">) => void;\n}\n\n/** SDK handle for an SRT announcer node. */\nexport class SrtAnnouncerNode extends BaseNode {\n /**\n * Forcibly disconnect the connection identified by `connectionId`. Can be\n * called at any time after the node is created — e.g. from an async\n * moderation decision made after `onConnect` already returned.\n */\n async disconnectSource(connectionId: string): Promise<void> {\n await this.client.trpc.disconnectSource.mutate({\n workflowId: this.workflowId,\n nodeId: this.nodeId,\n connectionId,\n });\n }\n}\n\nexport async function createSrtAnnouncerNode(\n workflow: WorkflowHandle,\n settings: SrtAnnouncerSettings,\n nodeId?: string,\n): Promise<SrtAnnouncerNode> {\n const id = nodeId ?? `srt_announcer_${uid(12)}`;\n const app = settings.app ?? \"live\";\n const node = new SrtAnnouncerNode(id, workflow.client, workflow.workflowId);\n const operations: SrtAnnouncerOperations = {\n disconnectSource: (connectionId) => node.disconnectSource(connectionId),\n };\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n if (evt.kind === \"STREAM_CONNECTED\" && evt.message !== undefined) {\n const info = JSON.parse(evt.message) as SrtConnectionInfo;\n settings.onConnect(info, operations);\n }\n if (evt.kind === \"STREAM_DISCONNECTED\" && evt.message !== undefined) {\n const info = JSON.parse(evt.message) as Pick<\n SrtConnectionInfo,\n \"app\" | \"sourceName\"\n >;\n settings.onDisconnect?.(info);\n }\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"SRT_ANNOUNCER\",\n workflowId: workflow.workflowId,\n nodeId: id,\n srtAnnouncer: {\n app,\n ...(settings.passphrase !== undefined && {\n passphrase: settings.passphrase,\n }),\n ...(settings.latencyMs !== undefined && {\n latencyMs: settings.latencyMs,\n }),\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport interface SrtReaderSettings extends NodeCallbacks {\n /** SRT app name. */\n app: string;\n /** Source name (stream key / stream ID suffix). */\n sourceName: string;\n /**\n * Retry policy for a failed FFmpeg pull from the internal MediaMTX relay\n * (e.g. a transient network blip). All fields optional; defaults to 5\n * attempts with 1s→10s exponential backoff before the node errors out.\n */\n retry?: {\n /** Max retries before giving up and marking the node ERROR. Default: 5. */\n maxAttempts?: number;\n /** Initial backoff delay in ms before the first retry. Default: 1000. */\n baseDelayMs?: number;\n /** Backoff delay ceiling in ms. Default: 10000. */\n maxDelayMs?: number;\n };\n /** Called when an SRT publisher connects and the stream is ready to read. */\n onPublish?: (info: { app: string; internalUrl: string }) => void;\n /**\n * Called once when the first data packet from this input arrives at the server.\n * @param wallClockMs `Date.now()` on the server at the moment the first MPEG-TS packet arrived.\n */\n onStreamStart?: (wallClockMs: number) => void;\n /** Called when the publisher disconnects (the underlying FFmpeg pull exits). */\n onStreamEnded?: () => void;\n}\n\n/** SDK handle for an SRT reader node. */\nexport class SrtReaderNode extends BaseNode {\n readonly settings: SrtReaderSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: SrtReaderSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n}\n\nexport async function createSrtReaderNode(\n workflow: WorkflowHandle,\n settings: SrtReaderSettings,\n nodeId?: string,\n): Promise<SrtReaderNode> {\n const id = nodeId ?? `srt_reader_${uid(12)}`;\n const node = new SrtReaderNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n if (evt.kind === \"READY\") {\n settings.onPublish?.({\n app: settings.app,\n internalUrl: evt.message ?? \"\",\n });\n }\n if (evt.kind === \"ACTIVE\" && evt.message !== undefined) {\n settings.onStreamStart?.(Number(evt.message));\n }\n if (evt.kind === \"ENDED\") settings.onStreamEnded?.();\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"SRT_READER\",\n workflowId: workflow.workflowId,\n nodeId: id,\n srtReader: {\n app: settings.app,\n sourceName: settings.sourceName,\n ...(settings.retry !== undefined && { retry: settings.retry }),\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport interface FileOutputSettings extends NodeCallbacks {\n /** Absolute path for the output file */\n fileName: string;\n /** Container format: \"mp4\" | \"ts\" | \"mkv\". Default: inferred from extension */\n format?: string;\n /** Overwrite `fileName` if it already exists. Default: `true`. */\n overwrite?: boolean;\n}\n\nexport class FileOutputNode extends BaseNode {\n readonly settings: FileOutputSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: FileOutputSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n}\n\nexport async function createFileOutputNode(\n workflow: WorkflowHandle,\n settings: FileOutputSettings,\n nodeId?: string,\n): Promise<FileOutputNode> {\n const id = nodeId ?? `file_output_${uid(12)}`;\n const node = new FileOutputNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"FILE_OUTPUT\",\n workflowId: workflow.workflowId,\n nodeId: id,\n fileOutput: {\n fileName: settings.fileName,\n format: settings.format ?? \"\",\n overwrite: settings.overwrite ?? true,\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport type HlsDestination =\n | { type: \"file\"; path: string }\n | {\n type: \"s3\";\n bucket: string;\n region: string;\n /** S3-compatible endpoint URL (e.g. Tigris, MinIO, Cloudflare R2). */\n endpoint?: string;\n /** Key prefix prepended to all uploaded paths. */\n keyPrefix?: string;\n /**\n * When true (default), a per-session timestamp suffix is appended to\n * `keyPrefix` so that browser-cached segments from a previous session are\n * never served for a new one. Set to false when the caller already\n * generates a unique prefix or when sub-paths are not supported.\n */\n makePrefixUnique?: boolean;\n accessKeyId: string;\n secretAccessKey: string;\n /**\n * `Cache-Control` header for `.m3u8` playlist objects.\n * Default: `\"no-cache, no-store, must-revalidate\"` — playlists must\n * never be served stale.\n */\n playlistCacheControl?: string;\n /**\n * `Cache-Control` header for segment objects (`.ts`, `.vtt`).\n * Default: `\"public, max-age=86400\"` — segments are immutable once\n * written, so they can be cached aggressively.\n */\n segmentsCacheControl?: string;\n }\n | {\n type: \"http\";\n /** Base URL that receives PUT/DELETE requests for each segment and playlist. */\n baseUrl: string;\n /** Extra headers sent with every PUT/DELETE request. */\n headers?: Record<string, string>;\n };\n\nexport interface HlsOutputSettings extends NodeCallbacks {\n /** One or more destinations to fan HLS output to. */\n destinations: HlsDestination[];\n /** Target segment duration in seconds. Default: 6 */\n segmentTime?: number;\n /**\n * Number of segments kept in the live playlist window.\n * 0 = keep all (VOD). Default: 5.\n */\n listSize?: number;\n /**\n * Node-level fallback audio codec, applied to audio streams whose\n * subscription does not specify its own `codec`. Omitted (or `copy`)\n * preserves the input audio codec — except when more than one audio source\n * is connected, where the default becomes `aac` so all alternate-audio\n * renditions share one codec (required by hls.js).\n *\n * For a caption-readiness buffer (the old `avDelayMs`), put `delayMs` on\n * each media subscription instead and leave caption subscriptions\n * undelayed.\n */\n audioCodec?: \"copy\" | string | null;\n /**\n * Node-level fallback video codec, applied to video streams whose\n * subscription does not specify its own `codec`. Omitted (or `copy`)\n * preserves the input video codec.\n */\n videoCodec?: \"copy\" | string | null;\n}\n\nexport class HlsOutputNode extends BaseNode {\n readonly settings: HlsOutputSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: HlsOutputSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n\n /**\n * Return the effective base path or URL for each configured destination in\n * the current session. Must be called after `subscribe()` so that the server\n * has wired the destinations. For S3 destinations this includes the\n * per-session subfolder appended by `makePrefixUnique`.\n */\n async effectivePaths(): Promise<string[]> {\n const result = await this.client.trpc.hlsOutputInfo.query({\n workflowId: this.workflowId,\n nodeId: this.nodeId,\n });\n return result.effectivePaths;\n }\n}\n\nexport async function createHlsOutputNode(\n workflow: WorkflowHandle,\n settings: HlsOutputSettings,\n nodeId?: string,\n): Promise<HlsOutputNode> {\n const id = nodeId ?? `hls_output_${uid(12)}`;\n const node = new HlsOutputNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"HLS_OUTPUT\",\n workflowId: workflow.workflowId,\n nodeId: id,\n hlsOutput: {\n destinations: settings.destinations,\n segmentTime: settings.segmentTime ?? 6,\n listSize: settings.listSize ?? 5,\n audioCodec: settings.audioCodec ?? undefined,\n videoCodec: settings.videoCodec ?? undefined,\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport interface RtmpOutputSettings extends NodeCallbacks {\n /** Full RTMP URL to push to, e.g. \"rtmp://live.twitch.tv/app/stream-key\" */\n url: string;\n /**\n * Called when the output FFmpeg process exits with an error after the node\n * has been created — e.g. the destination server refused the connection,\n * dropped mid-stream, or returned an unexpected error code.\n *\n * Errors that occur during node creation still reject the\n * `workflow.output.rtmp()` promise as usual.\n */\n onError?: (err: Error) => void;\n}\n\nexport class RtmpOutputNode extends BaseNode {\n readonly settings: RtmpOutputSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: RtmpOutputSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n}\n\nexport async function createRtmpOutputNode(\n workflow: WorkflowHandle,\n settings: RtmpOutputSettings,\n nodeId?: string,\n): Promise<RtmpOutputNode> {\n const id = nodeId ?? `rtmp_output_${uid(12)}`;\n const node = new RtmpOutputNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"RTMP_OUTPUT\",\n workflowId: workflow.workflowId,\n nodeId: id,\n rtmpOutput: { url: settings.url },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport interface SrtOutputSettings extends NodeCallbacks {\n /**\n * Full SRT URL to push to, e.g. `\"srt://host:port\"`.\n * Transport parameters (latency, passphrase, etc.) are passed as query\n * params: `\"srt://host:port?latency=200&passphrase=secret\"`.\n */\n url: string;\n /**\n * Called when the output FFmpeg process exits with an error after the node\n * has been created — e.g. the destination refused the connection or dropped\n * mid-stream.\n *\n * Errors that occur during node creation still reject the\n * `workflow.output.srt()` promise as usual.\n */\n onError?: (err: Error) => void;\n}\n\nexport class SrtOutputNode extends BaseNode {\n readonly settings: SrtOutputSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: SrtOutputSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n}\n\nexport async function createSrtOutputNode(\n workflow: WorkflowHandle,\n settings: SrtOutputSettings,\n nodeId?: string,\n): Promise<SrtOutputNode> {\n const id = nodeId ?? `srt_output_${uid(12)}`;\n const node = new SrtOutputNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"SRT_OUTPUT\",\n workflowId: workflow.workflowId,\n nodeId: id,\n srtOutput: { url: settings.url },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import type {\n AzureSpeechLocale,\n AzureTargetLanguageCode,\n AzureVoiceName,\n} from \"@remavideo/server\";\nimport { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport interface AzureTranslateSettings extends NodeCallbacks {\n /** Azure Speech resource key. */\n apiKey: string;\n /** Azure Speech resource region, e.g. `\"westeurope\"`. */\n region: string;\n /**\n * Source language code for the input audio, e.g. `\"en-US\"`. Autocompletes\n * common locales; any valid Azure locale works — see\n * https://learn.microsoft.com/azure/ai-services/speech-service/language-support?tabs=stt\n * for the full list.\n */\n sourceLanguageCode: AzureSpeechLocale;\n /**\n * Target language code for the translation output, e.g. `\"de\"`.\n * Autocompletes common codes; any valid Azure target works — see\n * https://learn.microsoft.com/azure/ai-services/speech-service/language-support?tabs=speech-translation\n * for the full list.\n */\n targetLanguageCode: AzureTargetLanguageCode;\n /**\n * Target-language neural voice used to synthesize the translation, e.g.\n * `\"de-DE-KatjaNeural\"`. Must match the target language. Autocompletes\n * common voices; any valid Azure voice works — see\n * https://learn.microsoft.com/azure/ai-services/speech-service/language-support?tabs=tts\n * for the full list.\n */\n voiceName: AzureVoiceName;\n /** Human-readable label shown in the dashboard. */\n label?: string;\n}\n\n/** Processor node that translates audio via Azure AI Speech's real-time translation + synthesis. */\nexport class AzureTranslateNode extends BaseNode {\n readonly settings: AzureTranslateSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: AzureTranslateSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n}\n\nexport async function createAzureTranslateNode(\n workflow: WorkflowHandle,\n settings: AzureTranslateSettings,\n nodeId?: string,\n): Promise<AzureTranslateNode> {\n const id = nodeId ?? `azure_translate_${uid(12)}`;\n const node = new AzureTranslateNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"AZURE_TRANSLATE\",\n workflowId: workflow.workflowId,\n nodeId: id,\n azureTranslate: {\n apiKey: settings.apiKey,\n region: settings.region,\n sourceLanguageCode: settings.sourceLanguageCode,\n targetLanguageCode: settings.targetLanguageCode,\n voiceName: settings.voiceName,\n label: settings.label,\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport interface BufferSettings extends NodeCallbacks {\n /** Time (ms) to hold back the input stream before forwarding it to subscribers. */\n bufferMs: number;\n}\n\n/**\n * Time-shift node. Subscribe it to exactly one source (any selector); it\n * re-emits that source delayed by `bufferMs`, shared so every downstream\n * subscriber sees the same delayed feed.\n */\nexport class BufferNode extends BaseNode {\n readonly settings: BufferSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: BufferSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n}\n\n/** Create a {@link BufferNode} on the server. */\nexport async function createBufferNode(\n workflow: WorkflowHandle,\n settings: BufferSettings,\n nodeId?: string,\n): Promise<BufferNode> {\n const id = nodeId ?? `buffer_${uid(12)}`;\n const node = new BufferNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"BUFFER\",\n workflowId: workflow.workflowId,\n nodeId: id,\n buffer: { bufferMs: settings.bufferMs },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport interface CambAiTranslateSettings extends NodeCallbacks {\n /** Camb.ai API key. */\n apiKey: string;\n /** BCP-47 source language code for the input audio (e.g. `\"en-US\"`). */\n sourceLanguageCode: string;\n /** BCP-47 target language code for the translation output (e.g. `\"de-DE\"`). */\n targetLanguageCode: string;\n /** Realtime translation model. Defaults to Camb's server-side default. */\n model?: \"lilac\" | \"violet\" | \"iris\" | \"orchid\";\n /** Cloned voice ID to synthesize the translation with. Defaults to a built-in voice for the target language. */\n voiceId?: number;\n /** Human-readable label shown in the dashboard. */\n label?: string;\n}\n\n/** Processor node that translates audio via the Camb.ai realtime speech-to-speech API. */\nexport class CambAiTranslateNode extends BaseNode {\n readonly settings: CambAiTranslateSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: CambAiTranslateSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n}\n\nexport async function createCambAiTranslateNode(\n workflow: WorkflowHandle,\n settings: CambAiTranslateSettings,\n nodeId?: string,\n): Promise<CambAiTranslateNode> {\n const id = nodeId ?? `camb_translate_${uid(12)}`;\n const node = new CambAiTranslateNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"CAMB_AI_TRANSLATE\",\n workflowId: workflow.workflowId,\n nodeId: id,\n cambAiTranslate: {\n apiKey: settings.apiKey,\n sourceLanguageCode: settings.sourceLanguageCode,\n targetLanguageCode: settings.targetLanguageCode,\n model: settings.model,\n voiceId: settings.voiceId,\n label: settings.label,\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport type CaptionMode = \"rollUp\" | \"popOn\" | \"paintOn\";\n\nexport interface Cea608EncoderSettings extends NodeCallbacks {\n /** Caption display mode. Default: \"rollUp\" */\n mode?: CaptionMode;\n /**\n * Maximum number of visible rows (2, 3, or 4). Default: 2.\n *\n * - **rollUp**: sets the scroll-window height (RU2/RU3/RU4 command).\n * - **popOn**: captions sent with `\\n`-separated lines are displayed\n * bottom-anchored; this value caps how many lines are shown at once.\n * E.g. `rows: 2` with `\"line one\\nline two\"` fills rows 14 and 15.\n */\n rows?: 2 | 3 | 4;\n /**\n * When `mode` is `\"popOn\"`, automatically word-wrap caption text to\n * CEA-608's 32-column display width before laying it out as a\n * bottom-anchored multi-line caption card (capped at `rows` lines) —\n * instead of requiring you to insert `\\n` yourself. Has no effect in\n * `\"rollUp\"`/`\"paintOn\"` modes, where each `send()` is naturally a single\n * line.\n *\n * Default: false.\n */\n autoWrap?: boolean;\n}\n\nexport class Cea608EncoderNode extends BaseNode {\n readonly settings: Cea608EncoderSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: Cea608EncoderSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n}\n\nexport async function createCea608EncoderNode(\n workflow: WorkflowHandle,\n settings: Cea608EncoderSettings = {},\n nodeId?: string,\n): Promise<Cea608EncoderNode> {\n const id = nodeId ?? `cea608_${uid(12)}`;\n const node = new Cea608EncoderNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"CEA608_ENCODER\",\n workflowId: workflow.workflowId,\n nodeId: id,\n cea608Encoder: {\n mode: settings.mode ?? \"rollUp\",\n rows: settings.rows ?? 2,\n autoWrap: settings.autoWrap ?? false,\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import type { MonitorEventMsg } from \"@remavideo/server\";\nimport { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport type { MonitorEventMsg };\n\nexport interface FfprobeMonitorSettings extends NodeCallbacks {\n /**\n * How often (ms) to re-run ffprobe on the attached stream.\n * Each cycle spawns a fresh ffprobe process, probes `probeSizeBytes` of data,\n * and publishes the result to the **stream-info** monitor.\n * Default: 30 000 ms.\n */\n intervalMs?: number;\n /**\n * Bytes of stream data to feed into each ffprobe invocation.\n * Larger values give ffprobe more context (e.g. long-GOP streams) at the\n * cost of a slightly longer first-result latency. Default: 1 048 576 (1 MB).\n */\n probeSizeBytes?: number;\n}\n\nexport class FfprobeMonitorNode extends BaseNode {\n readonly settings: FfprobeMonitorSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: FfprobeMonitorSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n\n /**\n * Subscribe to a monitor stream on this node.\n *\n * - `\"stream-info\"` — emits a `status` event (key/value map) every time a\n * probe cycle completes, containing codec names, resolution, frame rate,\n * bit rates, and format.\n * - `\"ffprobe-log\"` — emits `log` events with raw ffprobe stderr output.\n *\n * Returns an object with an `unsubscribe()` method; call it to stop receiving\n * events and release the tRPC subscription.\n */\n observe(\n monitorName: \"stream-info\" | \"ffprobe-log\",\n onData: (event: MonitorEventMsg) => void,\n onError?: (err: unknown) => void,\n ): { unsubscribe(): void } {\n return this.client.trpc.observeNode.subscribe(\n { workflowId: this.workflowId, nodeId: this.nodeId, monitorName },\n { onData, ...(onError !== undefined && { onError }) },\n );\n }\n}\n\nexport async function createFfprobeMonitorNode(\n workflow: WorkflowHandle,\n settings: FfprobeMonitorSettings = {},\n nodeId?: string,\n): Promise<FfprobeMonitorNode> {\n const id = nodeId ?? `ffprobe_${uid(12)}`;\n const node = new FfprobeMonitorNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"FFPROBE_MONITOR\",\n workflowId: workflow.workflowId,\n nodeId: id,\n ffprobeMonitor: {\n intervalMs: settings.intervalMs ?? 30_000,\n probeSizeBytes: settings.probeSizeBytes ?? 1_048_576,\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport interface GateSettings extends NodeCallbacks {\n /**\n * Number of distinct sources that must have emitted at least one item\n * before the gate opens. Default: wait for every subscribed source.\n */\n minReady?: number;\n /**\n * Force the gate open after this many ms even if `minReady` hasn't been\n * reached, using whatever sources are ready by then. Omit to wait\n * indefinitely — a source that never produces data would otherwise stall\n * the gate forever.\n */\n timeoutMs?: number;\n}\n\n/**\n * Synchronization barrier for fan-in from multiple, independently-timed\n * sources (e.g. two different transcription/translation services). Not to\n * be confused with the `gate` option on `.subscribe()` (`ActivationGate`),\n * which defers wiring based on upstream node *lifecycle*, not data.\n *\n * `GateNode` subscribes to every source immediately and queues everything\n * each one emits, then starts forwarding — for every source at once — only\n * once enough of them have produced their first item. It is a one-time\n * rendezvous, not a per-item join.\n *\n * Every source becomes its own keyed pass-through port on the gate — streams\n * are never merged. Name a port with `name` on the gate's subscription spec\n * (default: the source's index as a string) and read it downstream with\n * `port`:\n *\n * ```ts\n * await gate.subscribe([\n * { source: a, sourceSelector: \"ALL\", name: \"main\" },\n * { source: b, sourceSelector: \"ALL\", name: \"backup\" },\n * ]);\n * await out1.subscribe([{ source: gate, sourceSelector: \"ALL\", port: \"main\" }]);\n * await out2.subscribe([{ source: gate, sourceSelector: \"ALL\", port: \"backup\" }]);\n * ```\n *\n * With exactly one source of a kind, plain selector subscriptions (no\n * `port`) still resolve to it; with several same-kind sources the server\n * errors and asks for an explicit `port`.\n */\nexport class GateNode extends BaseNode {\n readonly settings: GateSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: GateSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n}\n\n/** Create a {@link GateNode} on the server. */\nexport async function createGateNode(\n workflow: WorkflowHandle,\n settings: GateSettings = {},\n nodeId?: string,\n): Promise<GateNode> {\n const id = nodeId ?? `gate_${uid(12)}`;\n const node = new GateNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"GATE\",\n workflowId: workflow.workflowId,\n nodeId: id,\n gate: {\n ...(settings.minReady !== undefined && {\n minReady: settings.minReady,\n }),\n ...(settings.timeoutMs !== undefined && {\n timeoutMs: settings.timeoutMs,\n }),\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport interface GeminiTranslateSettings extends NodeCallbacks {\n /** Gemini API key. */\n apiKey: string;\n /**\n * BCP-47 target language code for the translation output\n * (e.g. `\"it\"`, `\"es\"`, `\"fr\"`).\n */\n targetLanguageCode: string;\n /** Human-readable label shown in the dashboard. */\n label?: string;\n}\n\n/** Processor node that translates audio via the Gemini Live Translate API. */\nexport class GeminiTranslateNode extends BaseNode {\n readonly settings: GeminiTranslateSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: GeminiTranslateSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n}\n\nexport async function createGeminiTranslateNode(\n workflow: WorkflowHandle,\n settings: GeminiTranslateSettings,\n nodeId?: string,\n): Promise<GeminiTranslateNode> {\n const id = nodeId ?? `gemini_translate_${uid(12)}`;\n const node = new GeminiTranslateNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"GEMINI_TRANSLATE\",\n workflowId: workflow.workflowId,\n nodeId: id,\n geminiTranslate: {\n apiKey: settings.apiKey,\n targetLanguageCode: settings.targetLanguageCode,\n label: settings.label,\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import type { OpenAiTranslateTargetLanguage } from \"@remavideo/server\";\nimport { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport interface OpenAiTranslateSettings extends NodeCallbacks {\n /** OpenAI API key. */\n apiKey: string;\n /**\n * Target language code for the translation output — one of the 13\n * languages `gpt-realtime-translate` currently supports.\n */\n targetLanguageCode: OpenAiTranslateTargetLanguage;\n /** Human-readable label shown in the dashboard. */\n label?: string;\n}\n\n/** Processor node that translates audio via OpenAI's `gpt-realtime-translate` model. */\nexport class OpenAiTranslateNode extends BaseNode {\n readonly settings: OpenAiTranslateSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: OpenAiTranslateSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n}\n\nexport async function createOpenAiTranslateNode(\n workflow: WorkflowHandle,\n settings: OpenAiTranslateSettings,\n nodeId?: string,\n): Promise<OpenAiTranslateNode> {\n const id = nodeId ?? `openai_translate_${uid(12)}`;\n const node = new OpenAiTranslateNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"OPENAI_TRANSLATE\",\n workflowId: workflow.workflowId,\n nodeId: id,\n openAiTranslate: {\n apiKey: settings.apiKey,\n targetLanguageCode: settings.targetLanguageCode,\n label: settings.label,\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import type { AppRouter, HealthStatus } from \"@remavideo/server\";\nimport {\n createTRPCClient,\n httpLink,\n httpSubscriptionLink,\n splitLink,\n} from \"@trpc/client\";\nimport { EventSource } from \"eventsource\";\nimport superjson from \"superjson\";\n\nexport interface RemaClientOptions {\n host?: string;\n port?: number;\n onLogEvent?: (log: { level: string; message: string }) => void;\n onReady?: () => void;\n onFailedToConnect?: () => void;\n}\n\nexport class RemaClient {\n readonly trpc: ReturnType<typeof createTRPCClient<AppRouter>>;\n readonly options: Required<Pick<RemaClientOptions, \"host\" | \"port\">>;\n\n private constructor(\n trpc: ReturnType<typeof createTRPCClient<AppRouter>>,\n options: RemaClientOptions,\n ) {\n this.trpc = trpc;\n this.options = {\n host: options.host ?? \"127.0.0.1\",\n port: options.port ?? 8080,\n };\n }\n\n static async connect(opts: RemaClientOptions = {}): Promise<RemaClient> {\n const host = opts.host ?? \"127.0.0.1\";\n const port = opts.port ?? 8080;\n const url = `http://${host}:${port}/trpc`;\n\n const trpc = createTRPCClient<AppRouter>({\n links: [\n splitLink({\n condition: (op) => op.type === \"subscription\",\n true: httpSubscriptionLink({\n url,\n transformer: superjson,\n EventSource,\n }),\n false: httpLink({ url, transformer: superjson }),\n }),\n ],\n });\n\n try {\n await trpc.ping.query();\n opts.onReady?.();\n } catch (err) {\n opts.onFailedToConnect?.();\n throw new Error(\n `Failed to connect to rema server at ${host}:${port}: ${String(err)}`,\n );\n }\n\n return new RemaClient(trpc, opts);\n }\n\n /** No-op kept for API compatibility — SSE has no persistent connection to close. */\n close(): void {}\n\n /** Fetches the server's health status from the plain HTTP `/api/health` endpoint. */\n async health(): Promise<HealthStatus> {\n const url = `http://${this.options.host}:${this.options.port}/api/health`;\n const res = await fetch(url);\n if (!res.ok) {\n throw new Error(`Health check failed: ${res.status} ${res.statusText}`);\n }\n return (await res.json()) as HealthStatus;\n }\n}\n","import type { NodeEventMsg, WorkflowInfo } from \"@remavideo/server\";\nimport { TypedEmitter } from \"tiny-typed-emitter\";\nimport type { RemaClient } from \"./client.js\";\nimport {\n type CaptionSourceNode,\n type CaptionSourceSettings,\n createCaptionSourceNode,\n} from \"./input/caption-source.js\";\nimport {\n createFileInputNode,\n type FileInputNode,\n type FileInputSettings,\n} from \"./input/file.js\";\nimport {\n createNodeStreamInputNode,\n type NodeStreamInputNode,\n type NodeStreamInputSettings,\n} from \"./input/node-stream.js\";\nimport {\n createRtmpAnnouncerNode,\n type RtmpAnnouncerNode,\n type RtmpAnnouncerSettings,\n} from \"./input/rtmp-announcer.js\";\nimport {\n createRtmpMultiplexerNode,\n type RtmpMultiplexerNode,\n type RtmpMultiplexerSettings,\n} from \"./input/rtmp-multiplexer.js\";\nimport {\n createRtmpReaderNode,\n type RtmpReaderNode,\n type RtmpReaderSettings,\n} from \"./input/rtmp-reader.js\";\nimport {\n createSrtAnnouncerNode,\n type SrtAnnouncerNode,\n type SrtAnnouncerSettings,\n} from \"./input/srt-announcer.js\";\nimport {\n createSrtReaderNode,\n type SrtReaderNode,\n type SrtReaderSettings,\n} from \"./input/srt-reader.js\";\nimport {\n createFileOutputNode,\n type FileOutputNode,\n type FileOutputSettings,\n} from \"./output/file.js\";\nimport {\n createHlsOutputNode,\n type HlsOutputNode,\n type HlsOutputSettings,\n} from \"./output/hls.js\";\nimport {\n createRtmpOutputNode,\n type RtmpOutputNode,\n type RtmpOutputSettings,\n} from \"./output/rtmp.js\";\nimport {\n createSrtOutputNode,\n type SrtOutputNode,\n type SrtOutputSettings,\n} from \"./output/srt.js\";\nimport {\n type AudioMixerNode,\n type AudioMixerSettings,\n createAudioMixerNode,\n} from \"./processor/audio-mixer.js\";\nimport {\n type AzureTranslateNode,\n type AzureTranslateSettings,\n createAzureTranslateNode,\n} from \"./processor/azure-translate.js\";\nimport {\n type BufferNode,\n type BufferSettings,\n createBufferNode,\n} from \"./processor/buffer.js\";\nimport {\n type CambAiTranslateNode,\n type CambAiTranslateSettings,\n createCambAiTranslateNode,\n} from \"./processor/camb-ai-translate.js\";\nimport {\n type Cea608EncoderNode,\n type Cea608EncoderSettings,\n createCea608EncoderNode,\n} from \"./processor/cea608-encoder.js\";\nimport {\n createFfprobeMonitorNode,\n type FfprobeMonitorNode,\n type FfprobeMonitorSettings,\n} from \"./processor/ffprobe-monitor.js\";\nimport {\n createGateNode,\n type GateNode,\n type GateSettings,\n} from \"./processor/gate.js\";\nimport {\n createGeminiTranslateNode,\n type GeminiTranslateNode,\n type GeminiTranslateSettings,\n} from \"./processor/gemini-translate.js\";\nimport {\n createOpenAiTranslateNode,\n type OpenAiTranslateNode,\n type OpenAiTranslateSettings,\n} from \"./processor/openai-translate.js\";\n\nexport type { WorkflowInfo };\n\n/** Normalize a `catch`-clause/subscription value (typed `unknown`) into an `Error`. */\nfunction toError(err: unknown): Error {\n return err instanceof Error ? err : new Error(String(err));\n}\n\n/** Lifecycle callbacks available on a workflow. */\nexport interface WorkflowCallbacks {\n /**\n * Called if this workflow's worker process crashes. Workflow errors\n * otherwise only appear in server logs — this is the client-side signal.\n */\n onError?: (err: Error) => void;\n /** Called when the workflow closes, whether via `.close()` or a crash. */\n onClose?: () => void;\n}\n\n/** Events emitted by a workflow, mirroring {@link WorkflowCallbacks} for `.on()`-style consumers. */\nexport interface WorkflowEvents {\n error: (err: Error) => void;\n close: () => void;\n}\n\n/**\n * A workflow is an isolated sub-registry on the server.\n * All nodes created through a workflow handle are scoped to that workflow;\n * they cannot subscribe to nodes in other workflows.\n *\n * Obtain a workflow via `rema.workflow.create()`.\n */\nexport class Workflow extends TypedEmitter<WorkflowEvents> {\n readonly workflowId: string;\n /** Non-private: accessible to factory functions via WorkflowHandle. */\n readonly client: RemaClient;\n\n private readonly _callbacks: WorkflowCallbacks;\n private readonly _eventHandlers = new Map<\n string,\n Set<(evt: NodeEventMsg) => void>\n >();\n private _eventSub: { unsubscribe(): void } | null = null;\n private _stateSub: { unsubscribe(): void } | null = null;\n private _seenInList = false;\n private _erroredFired = false;\n private _closedFired = false;\n\n /** @internal — use `rema.workflow.create()` instead */\n constructor(\n workflowId: string,\n client: RemaClient,\n callbacks: WorkflowCallbacks = {},\n ) {\n super();\n this.workflowId = workflowId;\n this.client = client;\n this._callbacks = callbacks;\n this._startEventSubscription();\n this._startStateSubscription();\n }\n\n /**\n * Register a handler for events emitted by a specific node in this workflow.\n * Returns an unsubscribe function — call it when the node is closed.\n * @internal used by node factory functions\n */\n _onNodeEvent(\n nodeId: string,\n handler: (evt: NodeEventMsg) => void,\n ): () => void {\n let handlers = this._eventHandlers.get(nodeId);\n if (handlers === undefined) {\n handlers = new Set();\n this._eventHandlers.set(nodeId, handlers);\n }\n handlers.add(handler);\n return () => {\n this._eventHandlers.get(nodeId)?.delete(handler);\n };\n }\n\n private _startEventSubscription(): void {\n this._eventSub = this.client.trpc.workflow.events.subscribe(\n { workflowId: this.workflowId },\n {\n onData: (evt) => {\n this._eventHandlers.get(evt.nodeId)?.forEach((h) => {\n h(evt);\n });\n },\n onError: (err) => this._reportError(toError(err)),\n },\n );\n }\n\n /**\n * Watches the server's list of all workflows for this workflow's entry, so\n * `onError`/`onClose` can be derived without a dedicated per-workflow\n * subscription: a transition to `status: \"crashed\"` fires `onError`, and\n * the entry disappearing (graceful close) fires `onClose`.\n */\n private _startStateSubscription(): void {\n this._stateSub = this.client.trpc.workflow.state.subscribe(undefined, {\n onData: (workflows) => {\n const entry = workflows.find((w) => w.id === this.workflowId);\n if (entry !== undefined) {\n this._seenInList = true;\n if (entry.status === \"crashed\" && !this._erroredFired) {\n this._erroredFired = true;\n this._reportError(new Error(\"workflow worker crashed\"));\n }\n return;\n }\n if (this._seenInList) this._fireClose();\n },\n onError: (err) => this._reportError(toError(err)),\n });\n }\n\n /**\n * Report an error to this workflow's `onError` callback and its\n * `WorkflowEvents` emitter. Like `BaseNode`, `Workflow` extends Node's\n * `EventEmitter`, which throws synchronously from `emit()` when an\n * `\"error\"` event has no listener — so this only emits when someone has\n * opted in via `.on(\"error\", ...)`; the `onError` callback covers the\n * common case. The SDK does no logging of its own.\n */\n private _reportError(err: Error): void {\n this._callbacks.onError?.(err);\n if (this.listenerCount(\"error\") > 0) this.emit(\"error\", err);\n }\n\n private _fireClose(): void {\n if (this._closedFired) return;\n this._closedFired = true;\n this._eventSub?.unsubscribe();\n this._stateSub?.unsubscribe();\n this._callbacks.onClose?.();\n this.emit(\"close\");\n }\n\n // ─── Input nodes ─────────────────────────────────────────────────────────\n\n readonly input = {\n file: (\n settings: FileInputSettings,\n nodeId?: string,\n ): Promise<FileInputNode> => createFileInputNode(this, settings, nodeId),\n\n rtmpReader: (\n settings: RtmpReaderSettings,\n nodeId?: string,\n ): Promise<RtmpReaderNode> => createRtmpReaderNode(this, settings, nodeId),\n\n /** Announces RTMP publisher connections on an `app` without creating nodes. */\n rtmpAnnouncer: (\n settings: RtmpAnnouncerSettings,\n nodeId?: string,\n ): Promise<RtmpAnnouncerNode> =>\n createRtmpAnnouncerNode(this, settings, nodeId),\n\n /** Announces SRT publisher connections on an `app` without creating nodes. */\n srtAnnouncer: (\n settings: SrtAnnouncerSettings,\n nodeId?: string,\n ): Promise<SrtAnnouncerNode> =>\n createSrtAnnouncerNode(this, settings, nodeId),\n\n /** Reads an SRT stream ingested by MediaMTX, pulling it back via RTSP internally. */\n srtReader: (\n settings: SrtReaderSettings,\n nodeId?: string,\n ): Promise<SrtReaderNode> => createSrtReaderNode(this, settings, nodeId),\n\n /**\n * An RTMP multiplexer: accepts any number of simultaneous publishers under\n * the given `app`. For each connection `onStream` is called with a fresh\n * `RtmpStreamNode` that can be wired into its own encoder/output pipeline.\n */\n rtmpMultiplexer: (\n settings: RtmpMultiplexerSettings,\n nodeId?: string,\n ): Promise<RtmpMultiplexerNode> =>\n createRtmpMultiplexerNode(this, settings, nodeId),\n\n /** Format-agnostic caption schedule node. Send captions to it; wire it to a cea608() encoder. */\n captions: (\n settings: CaptionSourceSettings = {},\n nodeId?: string,\n ): Promise<CaptionSourceNode> =>\n createCaptionSourceNode(this, settings, nodeId),\n\n /**\n * Audio stream input node. Push a Node.js `Readable` (or any\n * `AsyncIterable<Buffer>`) directly into the pipeline by calling\n * `.stream(source)` on the returned node.\n */\n streamInput: (\n settings: NodeStreamInputSettings,\n nodeId?: string,\n ): Promise<NodeStreamInputNode> =>\n createNodeStreamInputNode(this, settings, nodeId),\n };\n\n // ─── Output nodes ─────────────────────────────────────────────────────────\n\n readonly output = {\n rtmp: (\n settings: RtmpOutputSettings,\n nodeId?: string,\n ): Promise<RtmpOutputNode> => createRtmpOutputNode(this, settings, nodeId),\n\n file: (\n settings: FileOutputSettings,\n nodeId?: string,\n ): Promise<FileOutputNode> => createFileOutputNode(this, settings, nodeId),\n\n /** HLS output node. Subscribe it to one or more media/subtitle sources. */\n hls: (\n settings: HlsOutputSettings,\n nodeId?: string,\n ): Promise<HlsOutputNode> => createHlsOutputNode(this, settings, nodeId),\n\n /** SRT output node. Pushes MPEG-TS over SRT to the given URL. */\n srt: (\n settings: SrtOutputSettings,\n nodeId?: string,\n ): Promise<SrtOutputNode> => createSrtOutputNode(this, settings, nodeId),\n };\n\n // ─── Processor nodes ──────────────────────────────────────────────────────\n\n readonly processor = {\n /** CEA-608/708 encoder node. Subscribe it to video + a captions() node. */\n cea608: (\n settings: Cea608EncoderSettings = {},\n nodeId?: string,\n ): Promise<Cea608EncoderNode> =>\n createCea608EncoderNode(this, settings, nodeId),\n\n /**\n * ffprobe monitor node.\n * Subscribe it to any media source; it periodically probes the live stream\n * and exposes codec/resolution/bitrate info via the `stream-info` monitor.\n */\n ffprobeMonitor: (\n settings: FfprobeMonitorSettings = {},\n nodeId?: string,\n ): Promise<FfprobeMonitorNode> =>\n createFfprobeMonitorNode(this, settings, nodeId),\n\n /**\n * Gemini Live Translate node.\n * Subscribe it to an audio/media source; it outputs translated audio-only\n * MPEG-TS using the Gemini Live Translate API.\n */\n geminiTranslate: (\n settings: GeminiTranslateSettings,\n nodeId?: string,\n ): Promise<GeminiTranslateNode> =>\n createGeminiTranslateNode(this, settings, nodeId),\n\n /**\n * Camb.ai realtime speech-to-speech translation node.\n * Subscribe it to an audio/media source; it outputs translated audio-only\n * MPEG-TS using the Camb.ai realtime translation API.\n */\n cambAiTranslate: (\n settings: CambAiTranslateSettings,\n nodeId?: string,\n ): Promise<CambAiTranslateNode> =>\n createCambAiTranslateNode(this, settings, nodeId),\n\n /**\n * OpenAI `gpt-realtime-translate` node.\n * Subscribe it to an audio/media source; it outputs translated audio-only\n * MPEG-TS using OpenAI's realtime translation API.\n */\n openAiTranslate: (\n settings: OpenAiTranslateSettings,\n nodeId?: string,\n ): Promise<OpenAiTranslateNode> =>\n createOpenAiTranslateNode(this, settings, nodeId),\n\n /**\n * Azure AI Speech real-time translation node.\n * Subscribe it to an audio/media source; it outputs translated audio-only\n * MPEG-TS using Azure Speech's real-time translation + synthesis API.\n */\n azureTranslate: (\n settings: AzureTranslateSettings,\n nodeId?: string,\n ): Promise<AzureTranslateNode> =>\n createAzureTranslateNode(this, settings, nodeId),\n\n /**\n * Time-shift node. Subscribe it to exactly one source; it re-emits that\n * source delayed by `bufferMs`, shared across every downstream\n * subscriber.\n */\n buffer: (settings: BufferSettings, nodeId?: string): Promise<BufferNode> =>\n createBufferNode(this, settings, nodeId),\n\n /**\n * Synchronization barrier for fan-in from multiple, independently-timed\n * sources. Subscribe it to two or more sources; it withholds all of them\n * until enough (`minReady`, default: all) have produced their first\n * item, then forwards everything live.\n */\n gate: (settings: GateSettings = {}, nodeId?: string): Promise<GateNode> =>\n createGateNode(this, settings, nodeId),\n\n /**\n * Audio mixer node. Subscribe it to exactly `settings.audio.length`\n * AUDIO/ALL sources, in order — the Nth `audio[]` entry configures the\n * Nth subscribed source. Outputs a single mixed audio feed.\n */\n audioMixer: (\n settings: AudioMixerSettings,\n nodeId?: string,\n ): Promise<AudioMixerNode> => createAudioMixerNode(this, settings, nodeId),\n };\n\n // ─── Lifecycle ────────────────────────────────────────────────────────────\n\n /**\n * Close this workflow and deactivate all its nodes on the server.\n * After calling this the workflow handle is no longer usable.\n *\n * The event/state subscriptions are kept open until the close mutation\n * resolves so that the server's per-node `CLOSED` events (dispatched to\n * each node's `onClose`/`onStatusChange` callbacks) are still received,\n * rather than tearing the subscriptions down before those events arrive.\n */\n async close(): Promise<void> {\n await this.client.trpc.workflow.close.mutate({\n workflowId: this.workflowId,\n });\n this._fireClose();\n }\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\n/** Per-source settings for one audio input to an `AudioMixerNode`. */\nexport interface AudioMixerInputSettings {\n /** Linear gain multiplier (1 = unity, 0.5 = -6dB). Default: 1. */\n volume?: number;\n /** Silences this input entirely, regardless of `volume`. Default: false. */\n muted?: boolean;\n /** Stereo balance: -1 (full left) .. 1 (full right), 0 = center. Default: 0. */\n pan?: number;\n /** High-pass filter cutoff (Hz), applied before mixing. Omit for no high-pass. */\n highpassHz?: number;\n /** Low-pass filter cutoff (Hz), applied before mixing. Omit for no low-pass. */\n lowpassHz?: number;\n}\n\nexport interface AudioMixerSettings extends NodeCallbacks {\n /**\n * One entry per audio source this node will be subscribed to, in the same\n * order — the Nth entry configures the Nth subscribed AUDIO/ALL source.\n * Subscribe with exactly `audio.length` sources.\n */\n audio: AudioMixerInputSettings[];\n}\n\nexport class AudioMixerNode extends BaseNode {\n readonly settings: AudioMixerSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: AudioMixerSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n}\n\nexport async function createAudioMixerNode(\n workflow: WorkflowHandle,\n settings: AudioMixerSettings,\n nodeId?: string,\n): Promise<AudioMixerNode> {\n const id = nodeId ?? `audio_mixer_${uid(12)}`;\n const node = new AudioMixerNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"AUDIO_MIXER\",\n workflowId: workflow.workflowId,\n nodeId: id,\n audioMixer: {\n audio: settings.audio.map((a) => ({\n volume: a.volume ?? 1,\n muted: a.muted ?? false,\n pan: a.pan ?? 0,\n ...(a.highpassHz !== undefined ? { highpassHz: a.highpassHz } : {}),\n ...(a.lowpassHz !== undefined ? { lowpassHz: a.lowpassHz } : {}),\n })),\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import type { HealthStatus, WorkflowInfo } from \"@remavideo/server\";\nimport { RemaClient, type RemaClientOptions } from \"./client.js\";\nimport { Workflow, type WorkflowCallbacks } from \"./workflow.js\";\n\nexport class Rema {\n private readonly client: RemaClient;\n private readonly _autoCloseWorkflows = new Set<Workflow>();\n\n private constructor(client: RemaClient) {\n this.client = client;\n }\n\n static async connect(opts: RemaClientOptions = {}): Promise<Rema> {\n const client = await RemaClient.connect(opts);\n return new Rema(client);\n }\n\n // ─── Workflow management ──────────────────────────────────────────────────\n\n readonly workflow = {\n /**\n * Create a new isolated workflow on the server.\n *\n * All nodes created through the returned `Workflow` handle are scoped to\n * this workflow; they cannot subscribe to nodes in other workflows.\n *\n * @param opts.name Human-readable label shown in the web UI.\n * @param opts.workflowId Optional stable ID. Server generates one if omitted.\n * @param opts.closeOnDisconnect When true, this workflow is closed automatically\n * when `rema.close()`/`wf.close()` is called, or\n * when the connection to the server is lost (e.g.\n * the process crashes) for more than a few seconds.\n * @param opts.parentId ID of an existing workflow to attach this one to as\n * a child (shown nested in the web UI).\n * @param opts.closeWithParent When true, this workflow is closed automatically\n * when its parent workflow is closed.\n * @param opts.onError Called if this workflow's worker process crashes.\n * @param opts.onClose Called when the workflow closes, whether via\n * `.close()` or a crash.\n */\n create: async (\n opts: {\n name?: string;\n workflowId?: string;\n closeOnDisconnect?: boolean;\n parentId?: string;\n closeWithParent?: boolean;\n } & WorkflowCallbacks = {},\n ): Promise<Workflow> => {\n const info = await this.client.trpc.workflow.create.mutate({\n workflowId: opts.workflowId,\n name: opts.name,\n parentId: opts.parentId,\n closeWithParent: opts.closeWithParent,\n closeOnDisconnect: opts.closeOnDisconnect,\n });\n const wf = new Workflow(info.id, this.client, {\n ...(opts.onError !== undefined && { onError: opts.onError }),\n ...(opts.onClose !== undefined && { onClose: opts.onClose }),\n });\n if (opts.closeOnDisconnect === true) {\n this._autoCloseWorkflows.add(wf);\n }\n return wf;\n },\n\n /** List all active workflows on the server. */\n list: (): Promise<WorkflowInfo[]> => this.client.trpc.workflow.list.query(),\n };\n\n /** Fetches the server's current health status (uptime, workflow counts, MediaMTX reachability). */\n health(): Promise<HealthStatus> {\n return this.client.health();\n }\n\n // ─── Lifecycle ────────────────────────────────────────────────────────────\n\n /**\n * Close all `closeOnDisconnect` workflows, then disconnect from the server.\n */\n async close(): Promise<void> {\n await Promise.allSettled(\n [...this._autoCloseWorkflows].map((wf) => wf.close()),\n );\n this.client.close();\n }\n}\n","/**\n * Stream selectors — used when calling node.subscribe() to pick which\n * streams flow from source to subscriber.\n */\n\nexport type StreamSelector = \"VIDEO\" | \"AUDIO\" | \"CAPTIONS\" | \"ALL\";\n\n// Narrowly typed (`as const`) so a spec literal like\n// `{ sourceSelector: selectAudio, audio: {...} }` discriminates the\n// SubscriptionSpec union correctly.\nexport const selectVideo = \"VIDEO\" as const;\nexport const selectAudio = \"AUDIO\" as const;\nexport const selectCaptions = \"CAPTIONS\" as const;\nexport const selectAll = \"ALL\" as const;\n"],"mappings":"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","names":["uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","TypedEmitter","uid","uid","toError","TypedEmitter"]}
1
+ {"version":3,"sources":["../src/input/caption-source.ts","../src/node.ts","../src/input/file.ts","../src/input/node-stream.ts","../src/input/rtmp-announcer.ts","../src/input/rtmp-multiplexer.ts","../src/input/rtmp-reader.ts","../src/input/srt-announcer.ts","../src/input/srt-reader.ts","../src/output/file.ts","../src/output/hls.ts","../src/output/hls-renditions.ts","../src/output/rtmp.ts","../src/output/srt.ts","../src/processor/azure-translate.ts","../src/processor/buffer.ts","../src/processor/camb-ai-translate.ts","../src/processor/cea608-encoder.ts","../src/processor/ffprobe-monitor.ts","../src/processor/gate.ts","../src/processor/gemini-translate.ts","../src/processor/openai-translate.ts","../src/client.ts","../src/workflow.ts","../src/processor/audio-mixer.ts","../src/rema.ts","../src/selectors.ts"],"sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport interface CaptionSourceSettings extends NodeCallbacks {\n /**\n * How many past caption entries to replay to an encoder that subscribes\n * after some captions have already been sent.\n *\n * - `0` (default) — no replay; each encoder only receives captions sent\n * after it connects. Correct for live captioning where old text is stale.\n * - `Infinity` — replay the full history. Use this when pre-scheduling\n * all captions upfront (e.g. from an SRT file) before the pipeline starts,\n * so encoders that activate later still see every entry.\n * - `N` — replay the last N entries only.\n */\n replayCount?: number;\n /**\n * BCP-47 language tag written into the HLS master playlist when this caption\n * source is wired as a subtitle track. Default: \"und\".\n */\n language?: string;\n /** Human-readable track label shown in player subtitle menus. Default: \"Subtitles\". */\n label?: string;\n /** Whether this track is pre-selected when the player first loads. Default: true. */\n defaultTrack?: boolean;\n /**\n * Maximum character length per caption line. When set, captions whose text\n * exceeds this length are automatically split into sequential chunks on the\n * server. Each chunk's display duration is proportional to its character\n * count relative to the original caption's total duration.\n */\n maxLineLength?: number;\n}\n\nexport class CaptionSourceNode extends BaseNode {\n readonly settings: CaptionSourceSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: CaptionSourceSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n\n /**\n * Queue caption text for injection into the video stream.\n *\n * @param options.startAt Stream-elapsed milliseconds (relative to the\n * first video frame received by the server) at which\n * to show the caption. The server holds the text and\n * injects it at the matching PTS, so you can schedule\n * captions before that point in the stream is reached.\n * Omit (or 0) to inject immediately at the next frame.\n * @param options.duration How long (ms) the caption stays visible before\n * being automatically erased. Required.\n */\n async send(\n text: string,\n options: { duration: number; startAt?: number },\n ): Promise<void> {\n await this.client.trpc.sendCaption.mutate({\n workflowId: this.workflowId,\n nodeId: this.nodeId,\n text,\n durationMs: options.duration,\n startAtMs: options.startAt ?? 0,\n });\n }\n\n /**\n * Parse raw SRT or WebVTT content and schedule all entries on the server.\n *\n * Use `replayCount: Infinity` when creating this node if the pipeline may\n * connect encoders after the captions are loaded.\n *\n * @returns The number of caption entries loaded.\n */\n async sendFile(content: string): Promise<{ count: number }> {\n return this.client.trpc.loadCaptionFile.mutate({\n workflowId: this.workflowId,\n nodeId: this.nodeId,\n content,\n });\n }\n\n /**\n * Read a local SRT or WebVTT file and schedule all its entries on the server.\n * The file is read client-side; only the content string is sent over the wire.\n *\n * @param filePath Absolute or relative path to the subtitle file.\n * @returns The number of caption entries loaded.\n */\n async sendFileByPath(filePath: string): Promise<{ count: number }> {\n const content = await readFile(filePath, \"utf-8\");\n return this.sendFile(content);\n }\n}\n\nexport async function createCaptionSourceNode(\n workflow: WorkflowHandle,\n settings: CaptionSourceSettings = {},\n nodeId?: string,\n): Promise<CaptionSourceNode> {\n const id = nodeId ?? `caption_source_${Date.now()}`;\n const node = new CaptionSourceNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"CAPTION_SOURCE\",\n workflowId: workflow.workflowId,\n nodeId: id,\n captionSource: {\n replayCount: settings.replayCount ?? 0,\n language: settings.language ?? \"und\",\n label: settings.label ?? \"Subtitles\",\n defaultTrack: settings.defaultTrack ?? true,\n ...(settings.maxLineLength !== undefined && {\n maxLineLength: settings.maxLineLength,\n }),\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import type {\n ActivationGate,\n NodeEventMsg,\n NodeState,\n TrackInfo,\n} from \"@remavideo/server\";\nimport { TypedEmitter } from \"tiny-typed-emitter\";\nimport type { RemaClient } from \"./client.js\";\n\n/** Lifecycle callbacks available on every node's settings object. */\nexport interface NodeCallbacks {\n /** Called when the node transitions into the `ERROR` state. */\n onError?: (err: Error) => void;\n /** Called on every lifecycle state transition (`READY`, `ACTIVE`, `ENDED`, `ERROR`, `CLOSED`). */\n onStatusChange?: (state: NodeState, message?: string) => void;\n /** Called when the node closes — locally via `.close()`, or remotely (workflow close, crash). */\n onClose?: () => void;\n}\n\n/** Events emitted by every node, mirroring {@link NodeCallbacks} for `.on()`-style consumers. */\nexport interface NodeEvents {\n error: (err: Error) => void;\n statusChange: (state: NodeState, message?: string) => void;\n close: () => void;\n}\n\nconst NODE_STATES = new Set<NodeState>([\n \"READY\",\n \"ACTIVE\",\n \"ENDED\",\n \"ERROR\",\n \"CLOSED\",\n]);\n\n/**\n * Report an error to a node's `onError` callback and its `NodeEvents`\n * emitter. The SDK does no logging of its own — the caller decides whether\n * and how an unhandled error surfaces, via `onError` or `.on(\"error\", ...)`.\n *\n * `BaseNode` extends Node's `EventEmitter` (via `tiny-typed-emitter`), which\n * throws synchronously from `emit()` when an `\"error\"` event has no\n * listener. Since callers typically only set the `onError` callback (not\n * `.on(\"error\", ...)`), emitting unconditionally would throw back into\n * whatever triggered this call — e.g. the `workflow.events` subscription's\n * `onData` handler, tearing down event delivery for the entire workflow.\n * Only emit when someone has opted in.\n */\nexport function reportNodeError(\n node: BaseNode,\n callbacks: NodeCallbacks,\n err: Error,\n): void {\n callbacks.onError?.(err);\n if (node.listenerCount(\"error\") > 0) node.emit(\"error\", err);\n}\n\n/**\n * Dispatch a raw node event to the generic lifecycle callbacks on a node's\n * settings object, and to the node's `NodeEvents` emitter. Call this from a\n * node factory's event handler alongside any node-type-specific event handling.\n */\nexport function dispatchNodeCallbacks(\n node: BaseNode,\n evt: NodeEventMsg,\n callbacks: NodeCallbacks,\n): void {\n if (NODE_STATES.has(evt.kind as NodeState)) {\n const state = evt.kind as NodeState;\n callbacks.onStatusChange?.(state, evt.message);\n node.emit(\"statusChange\", state, evt.message);\n }\n if (evt.kind === \"ERROR\") {\n reportNodeError(node, callbacks, new Error(evt.message ?? \"node error\"));\n }\n if (evt.kind === \"CLOSED\") {\n callbacks.onClose?.();\n node.emit(\"close\");\n }\n}\n\n/**\n * FFmpeg codec specification for one stream of a subscription.\n *\n * The receiving output node applies it where appropriate (e.g. per-rendition\n * `-c:v:N`/`-c:a:N` for HLS, `-c:v`/`-c:a` for RTMP/File outputs). Omitted =\n * stream copy.\n */\nexport interface CodecSpec {\n /** FFmpeg encoder name: `\"copy\"`, `\"aac\"`, `\"libx264\"`, … */\n name: string;\n /**\n * Extra encoder options keyed by bare FFmpeg option name (no stream\n * specifier), e.g. `{ preset: \"veryfast\", b: \"3M\" }`.\n */\n options?: Record<string, string | number>;\n}\n\n/**\n * Audio rendition metadata for HLS output nodes. Sets the language tag and\n * label that appear in the player's track menu. Ignored by non-HLS nodes.\n */\nexport interface AudioRenditionOptions {\n /** BCP-47 language tag, e.g. `\"en\"`, `\"fr\"`. Default: `\"und\"`. */\n language?: string;\n /** Label shown in player track menus. Default: `\"Audio\"`. */\n label?: string;\n /** Pre-select this track on first play. Default: `false`. */\n defaultTrack?: boolean;\n}\n\n/**\n * Video rendition metadata for HLS output nodes. Ignored by non-HLS nodes.\n */\nexport interface VideoRenditionOptions {\n /**\n * Label used to name the HLS variant playlist/segments (sanitized to a\n * URL-safe variant name).\n */\n label?: string;\n}\n\n/**\n * One rung of an HLS adaptive-bitrate (ABR) ladder — an independent\n * resolution/bitrate tier transcoded from the same video source, wired via\n * `VideoSubscriptionSpec.additionalRenditions` / `AllSubscriptionSpec.additionalRenditions`.\n * HLS-only; ignored by non-HLS output nodes.\n */\nexport interface VideoRendition {\n /**\n * Output height in pixels. Also derives the default label (`\"{height}p\"`)\n * and, when `width` is omitted, an aspect-preserving `scale=-2:height`\n * filter.\n */\n height: number;\n /** Output width in pixels. Omit to preserve the source aspect ratio. */\n width?: number;\n /** Target video bitrate — ffmpeg bitrate syntax (`\"3M\"`) or bits/sec. */\n bitrate: string | number;\n /** VBV maxrate. Default: same as `bitrate`. */\n maxrate?: string | number;\n /** VBV bufsize. Default: 2x `bitrate`. */\n bufsize?: string | number;\n /** libx264 preset. Default: `\"veryfast\"`. */\n preset?: string;\n /**\n * Encoder name override — escape hatch for hardware encoders (e.g.\n * `\"h264_videotoolbox\"`, `\"h264_nvenc\"`). Default: `\"libx264\"`. No\n * server-side hardware-encoder plumbing exists; passed through as-is.\n */\n codecName?: string;\n /**\n * Variant label. Default: `\"{height}p\"`. Must be unique across the\n * ladder — colliding labels are auto-suffixed server-side.\n */\n label?: string;\n /**\n * Extra raw codec options, merged in last — overrides any default above\n * (including the keyframe-alignment `force_key_frames` default).\n */\n extraOptions?: Record<string, string | number>;\n}\n\n/** Fields shared by every subscription variant. */\ninterface SubscriptionSpecBase {\n source: BaseNode;\n /**\n * Delay this stream by N milliseconds before it reaches the subscribing\n * node (applied server-side). Typical use: delay the A/V subscriptions so\n * a slower caption source has lead time.\n */\n delayMs?: number;\n /**\n * Read from a keyed output port of a multi-output source node (Gate).\n * Ports are named by the `name` field of the specs the gate itself was\n * subscribed with. Mutually exclusive with `track`.\n */\n port?: string;\n /**\n * When the *subscribing* node is a multi-output node (Gate), names the\n * pass-through port it creates for this source (default: the source's\n * index as a string). Ignored by other node types.\n */\n name?: string;\n}\n\n/** Subscribe to a video-only stream of a source node. */\nexport interface VideoSubscriptionSpec extends SubscriptionSpecBase {\n sourceSelector: \"VIDEO\";\n /**\n * Per-type video track index within the source container (0 = first video\n * track). Discover available indices with `await source.tracks()`.\n * Default: 0.\n */\n track?: number;\n /** Codec applied to the video stream by the receiving output node. */\n codec?: CodecSpec;\n /** Video rendition metadata (HLS). */\n video?: VideoRenditionOptions;\n /**\n * Additional ABR ladder tiers transcoded from this same source, each\n * becoming its own HLS variant alongside this subscription's own\n * rendition. HLS-only; ignored by non-HLS output nodes.\n */\n additionalRenditions?: VideoRendition[];\n /**\n * When true, this subscription's own rendition (this `codec`/`video`) is\n * NOT subscribed as a variant — only `additionalRenditions` are. HLS-only;\n * ignored by non-HLS output nodes.\n */\n omitOriginalRendition?: boolean;\n}\n\n/** Subscribe to an audio-only stream of a source node. */\nexport interface AudioSubscriptionSpec extends SubscriptionSpecBase {\n sourceSelector: \"AUDIO\";\n /**\n * Per-type audio track index within the source container (0 = first audio\n * track). Discover available indices with `await source.tracks()`.\n * Default: 0.\n */\n track?: number;\n /** Codec applied to the audio stream by the receiving output node. */\n codec?: CodecSpec;\n /** Audio rendition metadata (HLS). */\n audio?: AudioRenditionOptions;\n}\n\n/** Subscribe to the combined A/V stream of a source node. */\nexport interface AllSubscriptionSpec extends SubscriptionSpecBase {\n sourceSelector: \"ALL\";\n /** Codec applied to the video track by the receiving output node. */\n videoCodec?: CodecSpec;\n /** Codec applied to the audio track by the receiving output node. */\n audioCodec?: CodecSpec;\n /** Video rendition metadata (HLS). */\n video?: VideoRenditionOptions;\n /** Audio rendition metadata (HLS). */\n audio?: AudioRenditionOptions;\n /**\n * Additional ABR ladder tiers transcoded from this same source, each\n * becoming its own video-only HLS variant alongside this subscription's\n * own combined A/V rendition. HLS-only; ignored by non-HLS output nodes.\n *\n * `omitOriginalRendition: true` on an `ALL` spec also drops this\n * subscription's own audio (since `ALL` bundles audio+video) — subscribe\n * audio separately (`sourceSelector: \"AUDIO\"`) if you still need it.\n */\n additionalRenditions?: VideoRendition[];\n /**\n * When true, this subscription's own combined rendition is NOT\n * subscribed — only `additionalRenditions` are. See the audio caveat\n * above. HLS-only; ignored by non-HLS output nodes.\n */\n omitOriginalRendition?: boolean;\n}\n\n/** Subscribe to the caption stream of a source node. */\nexport interface CaptionsSubscriptionSpec extends SubscriptionSpecBase {\n sourceSelector: \"CAPTIONS\";\n}\n\n/**\n * One source wired into a subscribing node, discriminated by\n * `sourceSelector` so per-stream options (codec, rendition metadata) are\n * only available where they apply.\n */\nexport type SubscriptionSpec =\n | VideoSubscriptionSpec\n | AudioSubscriptionSpec\n | AllSubscriptionSpec\n | CaptionsSubscriptionSpec;\n\n/**\n * Minimal interface passed to factory functions. Defined here (not in workflow.ts)\n * to avoid a circular import: workflow.ts imports factory files, factory files\n * import this interface.\n */\nexport interface WorkflowHandle {\n readonly workflowId: string;\n readonly client: RemaClient;\n _onNodeEvent(\n nodeId: string,\n handler: (evt: NodeEventMsg) => void,\n ): () => void;\n}\n\n/**\n * Base class for all rema nodes.\n * Provides the .subscribe() method that wires nodes together on the server,\n * and a `NodeEvents` emitter (`.on(\"error\"/\"statusChange\"/\"close\", ...)`) as\n * an alternative to the `onError`/`onStatusChange`/`onClose` settings callbacks.\n */\nexport abstract class BaseNode extends TypedEmitter<NodeEvents> {\n readonly nodeId: string;\n readonly workflowId: string;\n protected readonly client: RemaClient;\n private _eventCleanup: (() => void) | null = null;\n private _callbacks: NodeCallbacks = {};\n\n constructor(nodeId: string, client: RemaClient, workflowId: string) {\n super();\n this.nodeId = nodeId;\n this.workflowId = workflowId;\n this.client = client;\n }\n\n /** Called by create functions to register the workflow event handler cleanup. */\n _setEventCleanup(fn: () => void): void {\n this._eventCleanup = fn;\n }\n\n /**\n * Called by create functions to register the node's lifecycle callbacks\n * (`onError`/`onStatusChange`/`onClose`), so `.close()` can invoke `onClose`\n * directly on a locally-initiated close.\n */\n _bindCallbacks(callbacks: NodeCallbacks): void {\n this._callbacks = callbacks;\n }\n\n /**\n * Subscribe this node to one or more source nodes.\n * This tells the server to route streams from the sources into this node.\n */\n async subscribe(\n specs: SubscriptionSpec[],\n options?: { gate?: ActivationGate },\n ): Promise<void> {\n await this.client.trpc.subscribeNode.mutate({\n workflowId: this.workflowId,\n nodeId: this.nodeId,\n sources: specs.map((s) => BaseNode._toSourceInput(s)),\n gate: options?.gate,\n });\n }\n\n /** Map one `SubscriptionSpec` to the wire shape expected by the server. */\n private static _toSourceInput(spec: SubscriptionSpec) {\n const base = {\n sourceNodeId: spec.source.nodeId,\n ...(spec.delayMs !== undefined ? { delayMs: spec.delayMs } : {}),\n ...(spec.port !== undefined ? { port: spec.port } : {}),\n ...(spec.name !== undefined ? { name: spec.name } : {}),\n };\n const audio = (a: AudioRenditionOptions) => ({\n language: a.language ?? \"und\",\n label: a.label ?? \"Audio\",\n defaultTrack: a.defaultTrack ?? false,\n });\n // `video` is only sent when a label is provided — it is the only field\n // and is required on the wire.\n const video = (v: VideoRenditionOptions) =>\n v.label !== undefined ? { video: { label: v.label } } : {};\n\n switch (spec.sourceSelector) {\n case \"CAPTIONS\":\n return { ...base, selector: \"CAPTIONS\" as const };\n case \"VIDEO\":\n return {\n ...base,\n selector: \"VIDEO\" as const,\n ...(spec.track !== undefined ? { track: spec.track } : {}),\n ...(spec.codec !== undefined ? { codec: spec.codec } : {}),\n ...(spec.video !== undefined ? video(spec.video) : {}),\n };\n case \"AUDIO\":\n return {\n ...base,\n selector: \"AUDIO\" as const,\n ...(spec.track !== undefined ? { track: spec.track } : {}),\n ...(spec.codec !== undefined ? { codec: spec.codec } : {}),\n ...(spec.audio !== undefined ? { audio: audio(spec.audio) } : {}),\n };\n case \"ALL\":\n return {\n ...base,\n selector: \"ALL\" as const,\n ...(spec.videoCodec !== undefined\n ? { videoCodec: spec.videoCodec }\n : {}),\n ...(spec.audioCodec !== undefined\n ? { audioCodec: spec.audioCodec }\n : {}),\n ...(spec.video !== undefined ? video(spec.video) : {}),\n ...(spec.audio !== undefined ? { audio: audio(spec.audio) } : {}),\n };\n }\n }\n\n /**\n * Probe this node's media stream (ffprobe, server-side) and return its\n * track list. Each entry's `track` is the per-type index to pass back as\n * `track` in a VIDEO/AUDIO subscription:\n *\n * ```ts\n * const tracks = await input.tracks();\n * const ita = tracks.find((t) => t.type === \"audio\" && t.language === \"ita\");\n * await output.subscribe([\n * { source: input, sourceSelector: \"VIDEO\" },\n * { source: input, sourceSelector: \"AUDIO\", track: ita!.track },\n * ]);\n * ```\n *\n * Probing consumes real-time-paced stream data, so it can take several\n * seconds; `probeSizeBytes` (default 1 MB) trades discovery depth for\n * latency. The server errors after ~15 s if the source produces no data\n * (e.g. no publisher connected yet, or a disabled file input).\n */\n async tracks(options?: { probeSizeBytes?: number }): Promise<TrackInfo[]> {\n return this.client.trpc.probeTracks.query({\n workflowId: this.workflowId,\n nodeId: this.nodeId,\n ...(options?.probeSizeBytes !== undefined\n ? { probeSizeBytes: options.probeSizeBytes }\n : {}),\n });\n }\n\n /** Tear down this node on the server. */\n async close(): Promise<void> {\n this._eventCleanup?.();\n await this.client.trpc.closeNode.mutate({\n workflowId: this.workflowId,\n nodeId: this.nodeId,\n });\n this._callbacks.onClose?.();\n this.emit(\"close\");\n }\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport interface FileInputSettings extends NodeCallbacks {\n /** Absolute path to the media file (MP4, TS, MKV, …) */\n fileName: string;\n /** Loop the file indefinitely. Default: false */\n loop?: boolean;\n /** Source name used to identify streams from this node */\n sourceName?: string;\n /**\n * Start in a disabled state — the file is not read until `.enable()` is called\n * (or the node is enabled from the web UI). Default: false.\n */\n disabled?: boolean;\n /** Called when the node transitions to the enabled / ready state */\n onReady?: () => void;\n /**\n * Called once when the first data packet from this input arrives at the server.\n * Use this to convert absolute timestamps to the stream-relative `startAt`\n * expected by `CaptionSourceNode.send()`:\n *\n * ```ts\n * onStreamStart(wallClockMs) {\n * const startAt = myAbsoluteCaptionMs - wallClockMs;\n * await captions.send(text, { startAt });\n * }\n * ```\n *\n * @param wallClockMs `Date.now()` on the server at the moment the first\n * MPEG-TS data packet arrived from this input.\n */\n onStreamStart?: (wallClockMs: number) => void;\n /** Called once the file has finished playing (reaches EOF). Never fires when `loop` is true. */\n onEnded?: () => void;\n}\n\nexport class FileInputNode extends BaseNode {\n readonly settings: FileInputSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: FileInputSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n\n /** Enable this node — starts reading the file and streaming to downstream nodes. */\n async enable(): Promise<void> {\n await this.client.trpc.enableNode.mutate({\n workflowId: this.workflowId,\n nodeId: this.nodeId,\n });\n }\n}\n\nexport async function createFileInputNode(\n workflow: WorkflowHandle,\n settings: FileInputSettings,\n nodeId?: string,\n): Promise<FileInputNode> {\n const id = nodeId ?? `file_input_${uid(12)}`;\n const node = new FileInputNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n if (evt.kind === \"READY\") settings.onReady?.();\n if (evt.kind === \"ACTIVE\" && evt.message !== undefined)\n settings.onStreamStart?.(Number(evt.message));\n if (evt.kind === \"ENDED\") settings.onEnded?.();\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"FILE_INPUT\",\n workflowId: workflow.workflowId,\n nodeId: id,\n fileInput: {\n fileName: settings.fileName,\n loop: settings.loop ?? false,\n sourceName: settings.sourceName,\n disabled: settings.disabled ?? false,\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport interface NodeStreamInputSettings extends NodeCallbacks {\n /**\n * FFmpeg format identifier for the incoming audio data.\n *\n * Raw PCM examples: `\"s16le\"` (16-bit signed LE), `\"f32le\"` (32-bit float LE).\n * Encoded examples: `\"wav\"`, `\"mp3\"`, `\"aac\"`, `\"flac\"`.\n */\n format: string;\n /** Sample rate in Hz. Required when `format` is a raw PCM format. */\n sampleRate?: number;\n /** Number of audio channels. Required when `format` is a raw PCM format. */\n channels?: number;\n /**\n * FFmpeg codec for the MPEG-TS output. Defaults to `\"aac\"`, which is safe\n * for any input format. Use `\"copy\"` only when the input is already\n * MPEG-TS-compatible AAC.\n */\n audioCodec?: string;\n /** Human-readable label shown in the dashboard. */\n label?: string;\n /** Called when the node transitions to the ready state on the server. */\n onReady?: () => void;\n /**\n * Called once when the first audio packet is written to the server.\n * The argument is `Date.now()` on the server at that moment.\n */\n onStreamStart?: (wallClockMs: number) => void;\n /** Called once the underlying FFmpeg process exits cleanly after `end()`. */\n onEnded?: () => void;\n}\n\nexport class NodeStreamInputNode extends BaseNode {\n readonly settings: NodeStreamInputSettings;\n private readonly _apiBase: string;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: NodeStreamInputSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n const { host, port } = workflow.client.options;\n this._apiBase = `http://${host}:${port}`;\n }\n\n /**\n * Push an audio stream to the server.\n *\n * Accepts any `AsyncIterable<Buffer | Uint8Array>`, which includes Node.js\n * `Readable` streams (async-iterable since Node 10) and async generators.\n * The call resolves when the source iterable is exhausted or the server\n * closes the connection.\n *\n * @example\n * ```ts\n * import { createReadStream } from \"node:fs\";\n * await audioInput.stream(createReadStream(\"audio.mp3\"));\n * ```\n *\n * @example\n * ```ts\n * // Raw PCM from a microphone via node-record-lpcm16\n * const mic = recorder.start({ sampleRate: 16000, channels: 1 });\n * await audioInput.stream(mic);\n * ```\n */\n async stream(source: AsyncIterable<Buffer | Uint8Array>): Promise<void> {\n const url = `${this._apiBase}/api/node-stream/${this.workflowId}/${this.nodeId}`;\n\n const body = new ReadableStream<Uint8Array>({\n async start(controller) {\n try {\n for await (const chunk of source) {\n controller.enqueue(\n chunk instanceof Uint8Array ? chunk : new Uint8Array(chunk),\n );\n }\n controller.close();\n } catch (err) {\n controller.error(err);\n }\n },\n });\n\n await fetch(url, {\n method: \"POST\",\n body,\n // Required for streaming request bodies in Node.js 18+ native fetch.\n duplex: \"half\",\n } as RequestInit);\n }\n}\n\nexport async function createNodeStreamInputNode(\n workflow: WorkflowHandle,\n settings: NodeStreamInputSettings,\n nodeId?: string,\n): Promise<NodeStreamInputNode> {\n const id = nodeId ?? `node_stream_${uid(12)}`;\n const node = new NodeStreamInputNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n if (evt.kind === \"READY\") settings.onReady?.();\n if (evt.kind === \"ACTIVE\" && evt.message !== undefined)\n settings.onStreamStart?.(Number(evt.message));\n if (evt.kind === \"ENDED\") settings.onEnded?.();\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"NODE_STREAM_INPUT\",\n workflowId: workflow.workflowId,\n nodeId: id,\n nodeStreamInput: {\n format: settings.format,\n ...(settings.sampleRate !== undefined && {\n sampleRate: settings.sampleRate,\n }),\n ...(settings.channels !== undefined && {\n channels: settings.channels,\n }),\n audioCodec: settings.audioCodec ?? \"aac\",\n ...(settings.label !== undefined && { label: settings.label }),\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\n/** Connection info passed to `onConnect` and `onDisconnect`. */\nexport interface RtmpConnectionInfo {\n app: string;\n sourceName: string;\n /**\n * MediaMTX connection id for this publisher, if it could be resolved at\n * connect time. Pass directly to `operations.disconnectSource`.\n */\n connectionId?: string;\n}\n\n/** Operations available on a live RTMP publisher connection. */\nexport interface RtmpAnnouncerOperations {\n /**\n * Forcibly disconnect the connection identified by `connectionId`. Safe to\n * call even if it already disconnected (resolves as a no-op).\n */\n disconnectSource(connectionId: string): Promise<void>;\n}\n\nexport interface RtmpAnnouncerSettings extends NodeCallbacks {\n /** RTMP app name to watch (default: \"live\"). */\n app?: string;\n /**\n * Called when a publisher connects. Create nodes in any workflow from\n * here, or call `operations.disconnectSource(info.connectionId)` to kick\n * the publisher synchronously (e.g. to reject an invalid `sourceName`).\n */\n onConnect: (\n info: RtmpConnectionInfo,\n operations: RtmpAnnouncerOperations,\n ) => void;\n /** Called when a publisher disconnects. */\n onDisconnect?: (info: RtmpConnectionInfo) => void;\n}\n\n/** SDK handle for an RTMP announcer node. */\nexport class RtmpAnnouncerNode extends BaseNode {\n /**\n * Forcibly disconnect the connection identified by `connectionId`. Can be\n * called at any time after the node is created — e.g. from an async\n * moderation decision made after `onConnect` already returned.\n */\n async disconnectSource(connectionId: string): Promise<void> {\n await this.client.trpc.disconnectSource.mutate({\n workflowId: this.workflowId,\n nodeId: this.nodeId,\n connectionId,\n });\n }\n}\n\nexport async function createRtmpAnnouncerNode(\n workflow: WorkflowHandle,\n settings: RtmpAnnouncerSettings,\n nodeId?: string,\n): Promise<RtmpAnnouncerNode> {\n const id = nodeId ?? `rtmp_announcer_${uid(12)}`;\n const app = settings.app ?? \"live\";\n const node = new RtmpAnnouncerNode(id, workflow.client, workflow.workflowId);\n const operations: RtmpAnnouncerOperations = {\n disconnectSource: (connectionId) => node.disconnectSource(connectionId),\n };\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n if (evt.kind === \"STREAM_CONNECTED\" && evt.message !== undefined) {\n const info = JSON.parse(evt.message) as RtmpConnectionInfo;\n settings.onConnect(info, operations);\n }\n if (evt.kind === \"STREAM_DISCONNECTED\" && evt.message !== undefined) {\n const info = JSON.parse(evt.message) as RtmpConnectionInfo;\n settings.onDisconnect?.(info);\n }\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"RTMP_ANNOUNCER\",\n workflowId: workflow.workflowId,\n nodeId: id,\n rtmpAnnouncer: { app },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport type { RemaClient } from \"../client.js\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n reportNodeError,\n type WorkflowHandle,\n} from \"../node.js\";\n\n/** Normalize a `catch`-clause value (which TypeScript types as `unknown`) into an `Error`. */\nfunction toError(err: unknown): Error {\n return err instanceof Error ? err : new Error(String(err));\n}\n\n// ── RtmpStreamNode ─────────────────────────────────────────────────────────────\n\n/**\n * Represents a single active RTMP stream created by an `RtmpMultiplexerNode`.\n *\n * The server creates this node automatically when a publisher connects. The SDK\n * wraps the server-side node ID so it can be passed as a source to downstream\n * nodes (encoders, outputs) via `subscribe()`.\n */\nexport class RtmpStreamNode extends BaseNode {\n readonly app: string;\n readonly sourceName: string;\n\n constructor(\n nodeId: string,\n client: RemaClient,\n app: string,\n sourceName: string,\n workflowId: string,\n ) {\n super(nodeId, client, workflowId);\n this.app = app;\n this.sourceName = sourceName;\n }\n}\n\n// ── RtmpMultiplexerNode ────────────────────────────────────────────────────────\n\n/**\n * SDK handle for an RTMP multiplexer node.\n *\n * Calling `close()` deactivates the multiplexer and all of its active stream\n * nodes on the server.\n */\nexport class RtmpMultiplexerNode extends BaseNode {}\n\n// ── Settings ──────────────────────────────────────────────────────────────────\n\nexport interface RtmpMultiplexerSettings extends NodeCallbacks {\n /** RTMP app name to listen on (default: \"live\"). */\n app?: string;\n\n /**\n * Called when a publisher connects. Return `{ accept: false }` to kick the\n * connection immediately; return `{ accept: true }` or omit the return value\n * to accept and trigger `onStream`.\n */\n onConnect?: (info: {\n app: string;\n sourceName: string;\n }) =>\n | Promise<{ accept: boolean } | undefined>\n | { accept: boolean }\n | undefined;\n\n /**\n * Called with the per-publisher `RtmpStreamNode` when a new RTMP stream is\n * accepted. Create your downstream pipeline (encoders, outputs) from this node.\n */\n onStream: (node: RtmpStreamNode) => Promise<void> | void;\n\n /**\n * Called when a publisher disconnects or is rejected. Use this to close any\n * downstream nodes you created in `onStream`.\n */\n onStreamEnd?: (node: RtmpStreamNode) => Promise<void> | void;\n}\n\n// ── Factory ───────────────────────────────────────────────────────────────────\n\nexport async function createRtmpMultiplexerNode(\n workflow: WorkflowHandle,\n settings: RtmpMultiplexerSettings,\n nodeId?: string,\n): Promise<RtmpMultiplexerNode> {\n const id = nodeId ?? `rtmp_mux_${uid(12)}`;\n const app = settings.app ?? \"live\";\n const node = new RtmpMultiplexerNode(\n id,\n workflow.client,\n workflow.workflowId,\n );\n\n // Track active stream nodes by sourceName for onStreamEnd lookup.\n const activeStreams = new Map<string, RtmpStreamNode>();\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n if (evt.kind === \"CHILD_CREATED\" && evt.message !== undefined) {\n const { childNodeId, sourceName } = JSON.parse(evt.message) as {\n childNodeId: string;\n sourceName: string;\n };\n\n Promise.resolve(settings.onConnect?.({ app, sourceName }))\n .then(async (result) => {\n if (result?.accept === false) {\n await workflow.client.trpc.rejectPublisher\n .mutate({\n workflowId: workflow.workflowId,\n nodeId: id,\n sourceName,\n })\n .catch((err: unknown) => {\n reportNodeError(node, settings, toError(err));\n });\n return;\n }\n const streamNode = new RtmpStreamNode(\n childNodeId,\n workflow.client,\n app,\n sourceName,\n workflow.workflowId,\n );\n activeStreams.set(sourceName, streamNode);\n await settings.onStream(streamNode);\n })\n .catch((err: unknown) => {\n reportNodeError(node, settings, toError(err));\n });\n return;\n }\n\n if (evt.kind === \"CHILD_CLOSED\" && evt.message !== undefined) {\n const { sourceName } = JSON.parse(evt.message) as { sourceName: string };\n const streamNode = activeStreams.get(sourceName);\n activeStreams.delete(sourceName);\n if (streamNode) {\n Promise.resolve(settings.onStreamEnd?.(streamNode)).catch(\n (err: unknown) => {\n reportNodeError(node, settings, toError(err));\n },\n );\n }\n }\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"RTMP_MULTIPLEXER\",\n workflowId: workflow.workflowId,\n nodeId: id,\n rtmpMultiplexer: { app, port: 1935 },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport interface RtmpReaderSettings extends NodeCallbacks {\n /** RTMP app name. */\n app: string;\n /** Source name (stream key). */\n sourceName: string;\n /**\n * Retry policy for a failed FFmpeg pull from the internal MediaMTX relay\n * (e.g. a transient network blip). All fields optional; defaults to 5\n * attempts with 1s→10s exponential backoff before the node errors out.\n */\n retry?: {\n /** Max retries before giving up and marking the node ERROR. Default: 5. */\n maxAttempts?: number;\n /** Initial backoff delay in ms before the first retry. Default: 1000. */\n baseDelayMs?: number;\n /** Backoff delay ceiling in ms. Default: 10000. */\n maxDelayMs?: number;\n };\n /** Called when an RTMP publisher connects. */\n onPublish?: (info: { app: string; internalUrl: string }) => void;\n /**\n * Called once when the first data packet from this input arrives at the server.\n * @param wallClockMs `Date.now()` on the server at the moment the first MPEG-TS packet arrived.\n */\n onStreamStart?: (wallClockMs: number) => void;\n /** Called when the publisher disconnects (the underlying FFmpeg pull exits). */\n onStreamEnded?: () => void;\n}\n\nexport class RtmpReaderNode extends BaseNode {\n readonly settings: RtmpReaderSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: RtmpReaderSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n}\n\nexport async function createRtmpReaderNode(\n workflow: WorkflowHandle,\n settings: RtmpReaderSettings,\n nodeId?: string,\n): Promise<RtmpReaderNode> {\n const id = nodeId ?? `rtmp_reader_${uid(12)}`;\n const node = new RtmpReaderNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n if (evt.kind === \"READY\") {\n settings.onPublish?.({\n app: settings.app,\n internalUrl: evt.message ?? \"\",\n });\n }\n if (evt.kind === \"ACTIVE\" && evt.message !== undefined) {\n settings.onStreamStart?.(Number(evt.message));\n }\n if (evt.kind === \"ENDED\") settings.onStreamEnded?.();\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"RTMP_READER\",\n workflowId: workflow.workflowId,\n nodeId: id,\n rtmpReader: {\n app: settings.app,\n sourceName: settings.sourceName,\n ...(settings.retry !== undefined && { retry: settings.retry }),\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\n/** Connection info passed to `onConnect` and `onDisconnect`. */\nexport interface SrtConnectionInfo {\n app: string;\n sourceName: string;\n /** Passphrase configured on the announcer, if any. */\n passphrase?: string;\n /** Negotiated SRT latency in milliseconds, if configured. */\n latencyMs?: number;\n /**\n * MediaMTX connection id for this publisher, if it could be resolved at\n * connect time. Pass directly to `operations.disconnectSource`.\n */\n connectionId?: string;\n}\n\n/** Operations available on a live SRT publisher connection. */\nexport interface SrtAnnouncerOperations {\n /**\n * Forcibly disconnect the connection identified by `connectionId`. Safe to\n * call even if it already disconnected (resolves as a no-op).\n */\n disconnectSource(connectionId: string): Promise<void>;\n}\n\nexport interface SrtAnnouncerSettings extends NodeCallbacks {\n /** SRT app name to watch (default: \"live\"). */\n app?: string;\n /** Passphrase to include in connection events (mirrors MediaMTX path config). */\n passphrase?: string;\n /** SRT latency in milliseconds to include in connection events. */\n latencyMs?: number;\n /**\n * Called when a publisher connects. Create nodes in any workflow from\n * here, or call `operations.disconnectSource(info.connectionId)` to kick\n * the publisher synchronously (e.g. to reject an invalid `sourceName`).\n */\n onConnect: (\n info: SrtConnectionInfo,\n operations: SrtAnnouncerOperations,\n ) => void;\n /** Called when a publisher disconnects. */\n onDisconnect?: (info: Pick<SrtConnectionInfo, \"app\" | \"sourceName\">) => void;\n}\n\n/** SDK handle for an SRT announcer node. */\nexport class SrtAnnouncerNode extends BaseNode {\n /**\n * Forcibly disconnect the connection identified by `connectionId`. Can be\n * called at any time after the node is created — e.g. from an async\n * moderation decision made after `onConnect` already returned.\n */\n async disconnectSource(connectionId: string): Promise<void> {\n await this.client.trpc.disconnectSource.mutate({\n workflowId: this.workflowId,\n nodeId: this.nodeId,\n connectionId,\n });\n }\n}\n\nexport async function createSrtAnnouncerNode(\n workflow: WorkflowHandle,\n settings: SrtAnnouncerSettings,\n nodeId?: string,\n): Promise<SrtAnnouncerNode> {\n const id = nodeId ?? `srt_announcer_${uid(12)}`;\n const app = settings.app ?? \"live\";\n const node = new SrtAnnouncerNode(id, workflow.client, workflow.workflowId);\n const operations: SrtAnnouncerOperations = {\n disconnectSource: (connectionId) => node.disconnectSource(connectionId),\n };\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n if (evt.kind === \"STREAM_CONNECTED\" && evt.message !== undefined) {\n const info = JSON.parse(evt.message) as SrtConnectionInfo;\n settings.onConnect(info, operations);\n }\n if (evt.kind === \"STREAM_DISCONNECTED\" && evt.message !== undefined) {\n const info = JSON.parse(evt.message) as Pick<\n SrtConnectionInfo,\n \"app\" | \"sourceName\"\n >;\n settings.onDisconnect?.(info);\n }\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"SRT_ANNOUNCER\",\n workflowId: workflow.workflowId,\n nodeId: id,\n srtAnnouncer: {\n app,\n ...(settings.passphrase !== undefined && {\n passphrase: settings.passphrase,\n }),\n ...(settings.latencyMs !== undefined && {\n latencyMs: settings.latencyMs,\n }),\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport interface SrtReaderSettings extends NodeCallbacks {\n /** SRT app name. */\n app: string;\n /** Source name (stream key / stream ID suffix). */\n sourceName: string;\n /**\n * Retry policy for a failed FFmpeg pull from the internal MediaMTX relay\n * (e.g. a transient network blip). All fields optional; defaults to 5\n * attempts with 1s→10s exponential backoff before the node errors out.\n */\n retry?: {\n /** Max retries before giving up and marking the node ERROR. Default: 5. */\n maxAttempts?: number;\n /** Initial backoff delay in ms before the first retry. Default: 1000. */\n baseDelayMs?: number;\n /** Backoff delay ceiling in ms. Default: 10000. */\n maxDelayMs?: number;\n };\n /** Called when an SRT publisher connects and the stream is ready to read. */\n onPublish?: (info: { app: string; internalUrl: string }) => void;\n /**\n * Called once when the first data packet from this input arrives at the server.\n * @param wallClockMs `Date.now()` on the server at the moment the first MPEG-TS packet arrived.\n */\n onStreamStart?: (wallClockMs: number) => void;\n /** Called when the publisher disconnects (the underlying FFmpeg pull exits). */\n onStreamEnded?: () => void;\n}\n\n/** SDK handle for an SRT reader node. */\nexport class SrtReaderNode extends BaseNode {\n readonly settings: SrtReaderSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: SrtReaderSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n}\n\nexport async function createSrtReaderNode(\n workflow: WorkflowHandle,\n settings: SrtReaderSettings,\n nodeId?: string,\n): Promise<SrtReaderNode> {\n const id = nodeId ?? `srt_reader_${uid(12)}`;\n const node = new SrtReaderNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n if (evt.kind === \"READY\") {\n settings.onPublish?.({\n app: settings.app,\n internalUrl: evt.message ?? \"\",\n });\n }\n if (evt.kind === \"ACTIVE\" && evt.message !== undefined) {\n settings.onStreamStart?.(Number(evt.message));\n }\n if (evt.kind === \"ENDED\") settings.onStreamEnded?.();\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"SRT_READER\",\n workflowId: workflow.workflowId,\n nodeId: id,\n srtReader: {\n app: settings.app,\n sourceName: settings.sourceName,\n ...(settings.retry !== undefined && { retry: settings.retry }),\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport interface FileOutputSettings extends NodeCallbacks {\n /** Absolute path for the output file */\n fileName: string;\n /** Container format: \"mp4\" | \"ts\" | \"mkv\". Default: inferred from extension */\n format?: string;\n /** Overwrite `fileName` if it already exists. Default: `true`. */\n overwrite?: boolean;\n}\n\nexport class FileOutputNode extends BaseNode {\n readonly settings: FileOutputSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: FileOutputSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n}\n\nexport async function createFileOutputNode(\n workflow: WorkflowHandle,\n settings: FileOutputSettings,\n nodeId?: string,\n): Promise<FileOutputNode> {\n const id = nodeId ?? `file_output_${uid(12)}`;\n const node = new FileOutputNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"FILE_OUTPUT\",\n workflowId: workflow.workflowId,\n nodeId: id,\n fileOutput: {\n fileName: settings.fileName,\n format: settings.format ?? \"\",\n overwrite: settings.overwrite ?? true,\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import type { ActivationGate } from \"@remavideo/server\";\nimport { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type SubscriptionSpec,\n type WorkflowHandle,\n} from \"../node.js\";\nimport { expandRenditionSpecs } from \"./hls-renditions.js\";\n\nexport type HlsDestination =\n | { type: \"file\"; path: string }\n | {\n type: \"s3\";\n bucket: string;\n region: string;\n /** S3-compatible endpoint URL (e.g. Tigris, MinIO, Cloudflare R2). */\n endpoint?: string;\n /** Key prefix prepended to all uploaded paths. */\n keyPrefix?: string;\n /**\n * When true (default), a per-session timestamp suffix is appended to\n * `keyPrefix` so that browser-cached segments from a previous session are\n * never served for a new one. Set to false when the caller already\n * generates a unique prefix or when sub-paths are not supported.\n */\n makePrefixUnique?: boolean;\n accessKeyId: string;\n secretAccessKey: string;\n /**\n * `Cache-Control` header for `.m3u8` playlist objects.\n * Default: `\"no-cache, no-store, must-revalidate\"` — playlists must\n * never be served stale.\n */\n playlistCacheControl?: string;\n /**\n * `Cache-Control` header for segment objects (`.ts`, `.vtt`).\n * Default: `\"public, max-age=86400\"` — segments are immutable once\n * written, so they can be cached aggressively.\n */\n segmentsCacheControl?: string;\n }\n | {\n type: \"http\";\n /** Base URL that receives PUT/DELETE requests for each segment and playlist. */\n baseUrl: string;\n /** Extra headers sent with every PUT/DELETE request. */\n headers?: Record<string, string>;\n };\n\nexport interface HlsOutputSettings extends NodeCallbacks {\n /** One or more destinations to fan HLS output to. */\n destinations: HlsDestination[];\n /** Target segment duration in seconds. Default: 6 */\n segmentTime?: number;\n /**\n * Number of segments kept in the live playlist window.\n * 0 = keep all (VOD). Default: 5.\n */\n listSize?: number;\n /**\n * Node-level fallback audio codec, applied to audio streams whose\n * subscription does not specify its own `codec`. Omitted (or `copy`)\n * preserves the input audio codec — except when more than one audio source\n * is connected, where the default becomes `aac` so all alternate-audio\n * renditions share one codec (required by hls.js).\n *\n * For a caption-readiness buffer (the old `avDelayMs`), put `delayMs` on\n * each media subscription instead and leave caption subscriptions\n * undelayed.\n */\n audioCodec?: \"copy\" | string | null;\n /**\n * Node-level fallback video codec, applied to video streams whose\n * subscription does not specify its own `codec`. Omitted (or `copy`)\n * preserves the input video codec.\n */\n videoCodec?: \"copy\" | string | null;\n}\n\nexport class HlsOutputNode extends BaseNode {\n readonly settings: HlsOutputSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: HlsOutputSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n\n /**\n * Return the effective base path or URL for each configured destination in\n * the current session. Must be called after `subscribe()` so that the server\n * has wired the destinations. For S3 destinations this includes the\n * per-session subfolder appended by `makePrefixUnique`.\n */\n async effectivePaths(): Promise<string[]> {\n const result = await this.client.trpc.hlsOutputInfo.query({\n workflowId: this.workflowId,\n nodeId: this.nodeId,\n });\n return result.effectivePaths;\n }\n\n /**\n * Subscribe this node to one or more source nodes, expanding any\n * `VIDEO`/`ALL` spec's `additionalRenditions` into extra HLS variants\n * first (see `VideoRendition`). Keyframe alignment for generated\n * renditions uses this node's own `segmentTime`.\n */\n override async subscribe(\n specs: SubscriptionSpec[],\n options?: { gate?: ActivationGate },\n ): Promise<void> {\n await super.subscribe(\n expandRenditionSpecs(specs, this.settings.segmentTime ?? 6),\n options,\n );\n }\n}\n\nexport async function createHlsOutputNode(\n workflow: WorkflowHandle,\n settings: HlsOutputSettings,\n nodeId?: string,\n): Promise<HlsOutputNode> {\n const id = nodeId ?? `hls_output_${uid(12)}`;\n const node = new HlsOutputNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"HLS_OUTPUT\",\n workflowId: workflow.workflowId,\n nodeId: id,\n hlsOutput: {\n destinations: settings.destinations,\n segmentTime: settings.segmentTime ?? 6,\n listSize: settings.listSize ?? 5,\n audioCodec: settings.audioCodec ?? undefined,\n videoCodec: settings.videoCodec ?? undefined,\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import type {\n AllSubscriptionSpec,\n BaseNode,\n SubscriptionSpec,\n VideoRendition,\n VideoSubscriptionSpec,\n} from \"../node.js\";\n\n/** Ladders beyond this total variant count get a one-time `console.warn`. */\nconst RENDITION_WARN_THRESHOLD = 3;\n\n/**\n * Parse an ffmpeg bitrate value (`\"3M\"`, `\"1500k\"`, or a bare bits/sec\n * number) to bits/sec, for deriving a default `bufsize`.\n */\nfunction bitsPerSecond(v: string | number): number {\n if (typeof v === \"number\") return v;\n const match = /^([\\d.]+)\\s*([kKmM]?)$/.exec(v.trim());\n if (!match) return Number(v) || 0;\n const value = Number(match[1]);\n const suffix = match[2]?.toLowerCase();\n if (suffix === \"k\") return value * 1_000;\n if (suffix === \"m\") return value * 1_000_000;\n return value;\n}\n\n/** Build one generated `VideoSubscriptionSpec` for a single ladder tier. */\nfunction buildRenditionSpec(\n source: BaseNode,\n track: number | undefined,\n r: VideoRendition,\n segmentTime: number,\n): VideoSubscriptionSpec {\n const bitrate = String(r.bitrate);\n const maxrate = r.maxrate !== undefined ? String(r.maxrate) : bitrate;\n const bufsize =\n r.bufsize !== undefined\n ? String(r.bufsize)\n : String(2 * bitsPerSecond(r.bitrate));\n\n return {\n source,\n sourceSelector: \"VIDEO\",\n ...(track !== undefined ? { track } : {}),\n codec: {\n name: r.codecName ?? \"libx264\",\n options: {\n preset: r.preset ?? \"veryfast\",\n b: bitrate,\n maxrate,\n bufsize,\n // fps-agnostic keyframe alignment so every rendition cuts HLS\n // segments at the same wall-clock points — required for clean ABR\n // switching (see VideoRendition docs / SDK docs CPU-cost section).\n force_key_frames: `expr:gte(t,n_forced*${segmentTime})`,\n // Per-stream options always get a `:v:N` specifier appended (see\n // `buildCodecArgs`) — `filter` (not the bare `vf` alias, which does\n // not accept a stream specifier: `-vf:v:N` is invalid FFmpeg syntax\n // and fails with AVERROR(EINVAL)) becomes the valid `-filter:v:N`.\n ...(r.width !== undefined\n ? { s: `${r.width}x${r.height}` }\n : { filter: `scale=-2:${r.height}` }),\n ...r.extraOptions,\n },\n },\n video: { label: r.label ?? `${r.height}p` },\n };\n}\n\n/**\n * Expand every `VIDEO`/`ALL` spec's `additionalRenditions` into extra\n * `VideoSubscriptionSpec` entries, returning the full flat list to actually\n * subscribe. Non-VIDEO/ALL specs (AUDIO, CAPTIONS) and VIDEO/ALL specs with\n * no `additionalRenditions` pass through unchanged. Pure — no network\n * calls.\n *\n * Emits one `console.warn` (advisory, not thrown) when the total number of\n * video variants across all specs exceeds a sane default ladder size —\n * CPU cost scales ~linearly with rendition count, dominated by encode: each\n * rendition is an independent decode+encode pass inside the HLS output's\n * one shared FFmpeg process (no single-decode `filter_complex split+scale`\n * optimization).\n */\nexport function expandRenditionSpecs(\n specs: SubscriptionSpec[],\n segmentTime: number,\n): SubscriptionSpec[] {\n const result: SubscriptionSpec[] = [];\n let videoVariantCount = 0;\n\n for (const spec of specs) {\n if (spec.sourceSelector !== \"VIDEO\" && spec.sourceSelector !== \"ALL\") {\n result.push(spec);\n continue;\n }\n\n const { additionalRenditions, omitOriginalRendition, ...rest } = spec as\n | VideoSubscriptionSpec\n | AllSubscriptionSpec;\n\n if (!additionalRenditions || additionalRenditions.length === 0) {\n result.push(spec);\n videoVariantCount++;\n continue;\n }\n\n if (!omitOriginalRendition) {\n result.push(rest as SubscriptionSpec);\n videoVariantCount++;\n }\n\n const track = spec.sourceSelector === \"VIDEO\" ? spec.track : undefined;\n for (const r of additionalRenditions) {\n result.push(buildRenditionSpec(spec.source, track, r, segmentTime));\n videoVariantCount++;\n }\n }\n\n if (videoVariantCount > RENDITION_WARN_THRESHOLD) {\n console.warn(\n `[rema] HLS output: ${videoVariantCount} video renditions means ${videoVariantCount} concurrent decode+encode passes inside one FFmpeg process — CPU cost scales ~linearly with rendition count, dominated by encode. Consider trimming tiers, a faster preset (\"superfast\"/\"ultrafast\"), or a hardware encoder via VideoRendition.codecName (e.g. \"h264_videotoolbox\", \"h264_nvenc\").`,\n );\n }\n\n return result;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport interface RtmpOutputSettings extends NodeCallbacks {\n /** Full RTMP URL to push to, e.g. \"rtmp://live.twitch.tv/app/stream-key\" */\n url: string;\n /**\n * Called when the output FFmpeg process exits with an error after the node\n * has been created — e.g. the destination server refused the connection,\n * dropped mid-stream, or returned an unexpected error code.\n *\n * Errors that occur during node creation still reject the\n * `workflow.output.rtmp()` promise as usual.\n */\n onError?: (err: Error) => void;\n}\n\nexport class RtmpOutputNode extends BaseNode {\n readonly settings: RtmpOutputSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: RtmpOutputSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n}\n\nexport async function createRtmpOutputNode(\n workflow: WorkflowHandle,\n settings: RtmpOutputSettings,\n nodeId?: string,\n): Promise<RtmpOutputNode> {\n const id = nodeId ?? `rtmp_output_${uid(12)}`;\n const node = new RtmpOutputNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"RTMP_OUTPUT\",\n workflowId: workflow.workflowId,\n nodeId: id,\n rtmpOutput: { url: settings.url },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport interface SrtOutputSettings extends NodeCallbacks {\n /**\n * Full SRT URL to push to, e.g. `\"srt://host:port\"`.\n * Transport parameters (latency, passphrase, etc.) are passed as query\n * params: `\"srt://host:port?latency=200&passphrase=secret\"`.\n */\n url: string;\n /**\n * Called when the output FFmpeg process exits with an error after the node\n * has been created — e.g. the destination refused the connection or dropped\n * mid-stream.\n *\n * Errors that occur during node creation still reject the\n * `workflow.output.srt()` promise as usual.\n */\n onError?: (err: Error) => void;\n}\n\nexport class SrtOutputNode extends BaseNode {\n readonly settings: SrtOutputSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: SrtOutputSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n}\n\nexport async function createSrtOutputNode(\n workflow: WorkflowHandle,\n settings: SrtOutputSettings,\n nodeId?: string,\n): Promise<SrtOutputNode> {\n const id = nodeId ?? `srt_output_${uid(12)}`;\n const node = new SrtOutputNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"SRT_OUTPUT\",\n workflowId: workflow.workflowId,\n nodeId: id,\n srtOutput: { url: settings.url },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import type {\n AzureSpeechLocale,\n AzureTargetLanguageCode,\n AzureVoiceName,\n} from \"@remavideo/server\";\nimport { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport interface AzureTranslateSettings extends NodeCallbacks {\n /** Azure Speech resource key. */\n apiKey: string;\n /** Azure Speech resource region, e.g. `\"westeurope\"`. */\n region: string;\n /**\n * Source language code for the input audio, e.g. `\"en-US\"`. Autocompletes\n * common locales; any valid Azure locale works — see\n * https://learn.microsoft.com/azure/ai-services/speech-service/language-support?tabs=stt\n * for the full list.\n */\n sourceLanguageCode: AzureSpeechLocale;\n /**\n * Target language code for the translation output, e.g. `\"de\"`.\n * Autocompletes common codes; any valid Azure target works — see\n * https://learn.microsoft.com/azure/ai-services/speech-service/language-support?tabs=speech-translation\n * for the full list.\n */\n targetLanguageCode: AzureTargetLanguageCode;\n /**\n * Target-language neural voice used to synthesize the translation, e.g.\n * `\"de-DE-KatjaNeural\"`. Must match the target language. Autocompletes\n * common voices; any valid Azure voice works — see\n * https://learn.microsoft.com/azure/ai-services/speech-service/language-support?tabs=tts\n * for the full list.\n */\n voiceName: AzureVoiceName;\n /** Human-readable label shown in the dashboard. */\n label?: string;\n}\n\n/** Processor node that translates audio via Azure AI Speech's real-time translation + synthesis. */\nexport class AzureTranslateNode extends BaseNode {\n readonly settings: AzureTranslateSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: AzureTranslateSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n}\n\nexport async function createAzureTranslateNode(\n workflow: WorkflowHandle,\n settings: AzureTranslateSettings,\n nodeId?: string,\n): Promise<AzureTranslateNode> {\n const id = nodeId ?? `azure_translate_${uid(12)}`;\n const node = new AzureTranslateNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"AZURE_TRANSLATE\",\n workflowId: workflow.workflowId,\n nodeId: id,\n azureTranslate: {\n apiKey: settings.apiKey,\n region: settings.region,\n sourceLanguageCode: settings.sourceLanguageCode,\n targetLanguageCode: settings.targetLanguageCode,\n voiceName: settings.voiceName,\n label: settings.label,\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport interface BufferSettings extends NodeCallbacks {\n /** Time (ms) to hold back the input stream before forwarding it to subscribers. */\n bufferMs: number;\n}\n\n/**\n * Time-shift node. Subscribe it to exactly one source (any selector); it\n * re-emits that source delayed by `bufferMs`, shared so every downstream\n * subscriber sees the same delayed feed.\n */\nexport class BufferNode extends BaseNode {\n readonly settings: BufferSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: BufferSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n}\n\n/** Create a {@link BufferNode} on the server. */\nexport async function createBufferNode(\n workflow: WorkflowHandle,\n settings: BufferSettings,\n nodeId?: string,\n): Promise<BufferNode> {\n const id = nodeId ?? `buffer_${uid(12)}`;\n const node = new BufferNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"BUFFER\",\n workflowId: workflow.workflowId,\n nodeId: id,\n buffer: { bufferMs: settings.bufferMs },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport interface CambAiTranslateSettings extends NodeCallbacks {\n /** Camb.ai API key. */\n apiKey: string;\n /** BCP-47 source language code for the input audio (e.g. `\"en-US\"`). */\n sourceLanguageCode: string;\n /** BCP-47 target language code for the translation output (e.g. `\"de-DE\"`). */\n targetLanguageCode: string;\n /** Realtime translation model. Defaults to Camb's server-side default. */\n model?: \"lilac\" | \"violet\" | \"iris\" | \"orchid\";\n /** Cloned voice ID to synthesize the translation with. Defaults to a built-in voice for the target language. */\n voiceId?: number;\n /** Human-readable label shown in the dashboard. */\n label?: string;\n}\n\n/** Processor node that translates audio via the Camb.ai realtime speech-to-speech API. */\nexport class CambAiTranslateNode extends BaseNode {\n readonly settings: CambAiTranslateSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: CambAiTranslateSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n}\n\nexport async function createCambAiTranslateNode(\n workflow: WorkflowHandle,\n settings: CambAiTranslateSettings,\n nodeId?: string,\n): Promise<CambAiTranslateNode> {\n const id = nodeId ?? `camb_translate_${uid(12)}`;\n const node = new CambAiTranslateNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"CAMB_AI_TRANSLATE\",\n workflowId: workflow.workflowId,\n nodeId: id,\n cambAiTranslate: {\n apiKey: settings.apiKey,\n sourceLanguageCode: settings.sourceLanguageCode,\n targetLanguageCode: settings.targetLanguageCode,\n model: settings.model,\n voiceId: settings.voiceId,\n label: settings.label,\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport type CaptionMode = \"rollUp\" | \"popOn\" | \"paintOn\";\n\nexport interface Cea608EncoderSettings extends NodeCallbacks {\n /** Caption display mode. Default: \"rollUp\" */\n mode?: CaptionMode;\n /**\n * Maximum number of visible rows (2, 3, or 4). Default: 2.\n *\n * - **rollUp**: sets the scroll-window height (RU2/RU3/RU4 command).\n * - **popOn**: captions sent with `\\n`-separated lines are displayed\n * bottom-anchored; this value caps how many lines are shown at once.\n * E.g. `rows: 2` with `\"line one\\nline two\"` fills rows 14 and 15.\n */\n rows?: 2 | 3 | 4;\n /**\n * When `mode` is `\"popOn\"`, automatically word-wrap caption text to\n * CEA-608's 32-column display width before laying it out as a\n * bottom-anchored multi-line caption card (capped at `rows` lines) —\n * instead of requiring you to insert `\\n` yourself. Has no effect in\n * `\"rollUp\"`/`\"paintOn\"` modes, where each `send()` is naturally a single\n * line.\n *\n * Default: false.\n */\n autoWrap?: boolean;\n}\n\nexport class Cea608EncoderNode extends BaseNode {\n readonly settings: Cea608EncoderSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: Cea608EncoderSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n}\n\nexport async function createCea608EncoderNode(\n workflow: WorkflowHandle,\n settings: Cea608EncoderSettings = {},\n nodeId?: string,\n): Promise<Cea608EncoderNode> {\n const id = nodeId ?? `cea608_${uid(12)}`;\n const node = new Cea608EncoderNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"CEA608_ENCODER\",\n workflowId: workflow.workflowId,\n nodeId: id,\n cea608Encoder: {\n mode: settings.mode ?? \"rollUp\",\n rows: settings.rows ?? 2,\n autoWrap: settings.autoWrap ?? false,\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import type { MonitorEventMsg } from \"@remavideo/server\";\nimport { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport type { MonitorEventMsg };\n\nexport interface FfprobeMonitorSettings extends NodeCallbacks {\n /**\n * How often (ms) to re-run ffprobe on the attached stream.\n * Each cycle spawns a fresh ffprobe process, probes `probeSizeBytes` of data,\n * and publishes the result to the **stream-info** monitor.\n * Default: 30 000 ms.\n */\n intervalMs?: number;\n /**\n * Bytes of stream data to feed into each ffprobe invocation.\n * Larger values give ffprobe more context (e.g. long-GOP streams) at the\n * cost of a slightly longer first-result latency. Default: 1 048 576 (1 MB).\n */\n probeSizeBytes?: number;\n}\n\nexport class FfprobeMonitorNode extends BaseNode {\n readonly settings: FfprobeMonitorSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: FfprobeMonitorSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n\n /**\n * Subscribe to a monitor stream on this node.\n *\n * - `\"stream-info\"` — emits a `status` event (key/value map) every time a\n * probe cycle completes, containing codec names, resolution, frame rate,\n * bit rates, and format.\n * - `\"ffprobe-log\"` — emits `log` events with raw ffprobe stderr output.\n *\n * Returns an object with an `unsubscribe()` method; call it to stop receiving\n * events and release the tRPC subscription.\n */\n observe(\n monitorName: \"stream-info\" | \"ffprobe-log\",\n onData: (event: MonitorEventMsg) => void,\n onError?: (err: unknown) => void,\n ): { unsubscribe(): void } {\n return this.client.trpc.observeNode.subscribe(\n { workflowId: this.workflowId, nodeId: this.nodeId, monitorName },\n { onData, ...(onError !== undefined && { onError }) },\n );\n }\n}\n\nexport async function createFfprobeMonitorNode(\n workflow: WorkflowHandle,\n settings: FfprobeMonitorSettings = {},\n nodeId?: string,\n): Promise<FfprobeMonitorNode> {\n const id = nodeId ?? `ffprobe_${uid(12)}`;\n const node = new FfprobeMonitorNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"FFPROBE_MONITOR\",\n workflowId: workflow.workflowId,\n nodeId: id,\n ffprobeMonitor: {\n intervalMs: settings.intervalMs ?? 30_000,\n probeSizeBytes: settings.probeSizeBytes ?? 1_048_576,\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport interface GateSettings extends NodeCallbacks {\n /**\n * Number of distinct sources that must have emitted at least one item\n * before the gate opens. Default: wait for every subscribed source.\n */\n minReady?: number;\n /**\n * Force the gate open after this many ms even if `minReady` hasn't been\n * reached, using whatever sources are ready by then. Omit to wait\n * indefinitely — a source that never produces data would otherwise stall\n * the gate forever.\n */\n timeoutMs?: number;\n}\n\n/**\n * Synchronization barrier for fan-in from multiple, independently-timed\n * sources (e.g. two different transcription/translation services). Not to\n * be confused with the `gate` option on `.subscribe()` (`ActivationGate`),\n * which defers wiring based on upstream node *lifecycle*, not data.\n *\n * `GateNode` subscribes to every source immediately and queues everything\n * each one emits, then starts forwarding — for every source at once — only\n * once enough of them have produced their first item. It is a one-time\n * rendezvous, not a per-item join.\n *\n * Every source becomes its own keyed pass-through port on the gate — streams\n * are never merged. Name a port with `name` on the gate's subscription spec\n * (default: the source's index as a string) and read it downstream with\n * `port`:\n *\n * ```ts\n * await gate.subscribe([\n * { source: a, sourceSelector: \"ALL\", name: \"main\" },\n * { source: b, sourceSelector: \"ALL\", name: \"backup\" },\n * ]);\n * await out1.subscribe([{ source: gate, sourceSelector: \"ALL\", port: \"main\" }]);\n * await out2.subscribe([{ source: gate, sourceSelector: \"ALL\", port: \"backup\" }]);\n * ```\n *\n * With exactly one source of a kind, plain selector subscriptions (no\n * `port`) still resolve to it; with several same-kind sources the server\n * errors and asks for an explicit `port`.\n */\nexport class GateNode extends BaseNode {\n readonly settings: GateSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: GateSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n}\n\n/** Create a {@link GateNode} on the server. */\nexport async function createGateNode(\n workflow: WorkflowHandle,\n settings: GateSettings = {},\n nodeId?: string,\n): Promise<GateNode> {\n const id = nodeId ?? `gate_${uid(12)}`;\n const node = new GateNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"GATE\",\n workflowId: workflow.workflowId,\n nodeId: id,\n gate: {\n ...(settings.minReady !== undefined && {\n minReady: settings.minReady,\n }),\n ...(settings.timeoutMs !== undefined && {\n timeoutMs: settings.timeoutMs,\n }),\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport interface GeminiTranslateSettings extends NodeCallbacks {\n /** Gemini API key. */\n apiKey: string;\n /**\n * BCP-47 target language code for the translation output\n * (e.g. `\"it\"`, `\"es\"`, `\"fr\"`).\n */\n targetLanguageCode: string;\n /** Human-readable label shown in the dashboard. */\n label?: string;\n}\n\n/** Processor node that translates audio via the Gemini Live Translate API. */\nexport class GeminiTranslateNode extends BaseNode {\n readonly settings: GeminiTranslateSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: GeminiTranslateSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n}\n\nexport async function createGeminiTranslateNode(\n workflow: WorkflowHandle,\n settings: GeminiTranslateSettings,\n nodeId?: string,\n): Promise<GeminiTranslateNode> {\n const id = nodeId ?? `gemini_translate_${uid(12)}`;\n const node = new GeminiTranslateNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"GEMINI_TRANSLATE\",\n workflowId: workflow.workflowId,\n nodeId: id,\n geminiTranslate: {\n apiKey: settings.apiKey,\n targetLanguageCode: settings.targetLanguageCode,\n label: settings.label,\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import type { OpenAiTranslateTargetLanguage } from \"@remavideo/server\";\nimport { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\nexport interface OpenAiTranslateSettings extends NodeCallbacks {\n /** OpenAI API key. */\n apiKey: string;\n /**\n * Target language code for the translation output — one of the 13\n * languages `gpt-realtime-translate` currently supports.\n */\n targetLanguageCode: OpenAiTranslateTargetLanguage;\n /** Human-readable label shown in the dashboard. */\n label?: string;\n}\n\n/** Processor node that translates audio via OpenAI's `gpt-realtime-translate` model. */\nexport class OpenAiTranslateNode extends BaseNode {\n readonly settings: OpenAiTranslateSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: OpenAiTranslateSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n}\n\nexport async function createOpenAiTranslateNode(\n workflow: WorkflowHandle,\n settings: OpenAiTranslateSettings,\n nodeId?: string,\n): Promise<OpenAiTranslateNode> {\n const id = nodeId ?? `openai_translate_${uid(12)}`;\n const node = new OpenAiTranslateNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"OPENAI_TRANSLATE\",\n workflowId: workflow.workflowId,\n nodeId: id,\n openAiTranslate: {\n apiKey: settings.apiKey,\n targetLanguageCode: settings.targetLanguageCode,\n label: settings.label,\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import type { AppRouter, HealthStatus } from \"@remavideo/server\";\nimport {\n createTRPCClient,\n httpLink,\n httpSubscriptionLink,\n splitLink,\n} from \"@trpc/client\";\nimport { EventSource } from \"eventsource\";\nimport superjson from \"superjson\";\n\nexport interface RemaClientOptions {\n host?: string;\n port?: number;\n onLogEvent?: (log: { level: string; message: string }) => void;\n onReady?: () => void;\n onFailedToConnect?: () => void;\n}\n\nexport class RemaClient {\n readonly trpc: ReturnType<typeof createTRPCClient<AppRouter>>;\n readonly options: Required<Pick<RemaClientOptions, \"host\" | \"port\">>;\n\n private constructor(\n trpc: ReturnType<typeof createTRPCClient<AppRouter>>,\n options: RemaClientOptions,\n ) {\n this.trpc = trpc;\n this.options = {\n host: options.host ?? \"127.0.0.1\",\n port: options.port ?? 8080,\n };\n }\n\n static async connect(opts: RemaClientOptions = {}): Promise<RemaClient> {\n const host = opts.host ?? \"127.0.0.1\";\n const port = opts.port ?? 8080;\n const url = `http://${host}:${port}/trpc`;\n\n const trpc = createTRPCClient<AppRouter>({\n links: [\n splitLink({\n condition: (op) => op.type === \"subscription\",\n true: httpSubscriptionLink({\n url,\n transformer: superjson,\n EventSource,\n }),\n false: httpLink({ url, transformer: superjson }),\n }),\n ],\n });\n\n try {\n await trpc.ping.query();\n opts.onReady?.();\n } catch (err) {\n opts.onFailedToConnect?.();\n throw new Error(\n `Failed to connect to rema server at ${host}:${port}: ${String(err)}`,\n );\n }\n\n return new RemaClient(trpc, opts);\n }\n\n /** No-op kept for API compatibility — SSE has no persistent connection to close. */\n close(): void {}\n\n /** Fetches the server's health status from the plain HTTP `/api/health` endpoint. */\n async health(): Promise<HealthStatus> {\n const url = `http://${this.options.host}:${this.options.port}/api/health`;\n const res = await fetch(url);\n if (!res.ok) {\n throw new Error(`Health check failed: ${res.status} ${res.statusText}`);\n }\n return (await res.json()) as HealthStatus;\n }\n}\n","import type { NodeEventMsg, WorkflowInfo } from \"@remavideo/server\";\nimport { TypedEmitter } from \"tiny-typed-emitter\";\nimport type { RemaClient } from \"./client.js\";\nimport {\n type CaptionSourceNode,\n type CaptionSourceSettings,\n createCaptionSourceNode,\n} from \"./input/caption-source.js\";\nimport {\n createFileInputNode,\n type FileInputNode,\n type FileInputSettings,\n} from \"./input/file.js\";\nimport {\n createNodeStreamInputNode,\n type NodeStreamInputNode,\n type NodeStreamInputSettings,\n} from \"./input/node-stream.js\";\nimport {\n createRtmpAnnouncerNode,\n type RtmpAnnouncerNode,\n type RtmpAnnouncerSettings,\n} from \"./input/rtmp-announcer.js\";\nimport {\n createRtmpMultiplexerNode,\n type RtmpMultiplexerNode,\n type RtmpMultiplexerSettings,\n} from \"./input/rtmp-multiplexer.js\";\nimport {\n createRtmpReaderNode,\n type RtmpReaderNode,\n type RtmpReaderSettings,\n} from \"./input/rtmp-reader.js\";\nimport {\n createSrtAnnouncerNode,\n type SrtAnnouncerNode,\n type SrtAnnouncerSettings,\n} from \"./input/srt-announcer.js\";\nimport {\n createSrtReaderNode,\n type SrtReaderNode,\n type SrtReaderSettings,\n} from \"./input/srt-reader.js\";\nimport {\n createFileOutputNode,\n type FileOutputNode,\n type FileOutputSettings,\n} from \"./output/file.js\";\nimport {\n createHlsOutputNode,\n type HlsOutputNode,\n type HlsOutputSettings,\n} from \"./output/hls.js\";\nimport {\n createRtmpOutputNode,\n type RtmpOutputNode,\n type RtmpOutputSettings,\n} from \"./output/rtmp.js\";\nimport {\n createSrtOutputNode,\n type SrtOutputNode,\n type SrtOutputSettings,\n} from \"./output/srt.js\";\nimport {\n type AudioMixerNode,\n type AudioMixerSettings,\n createAudioMixerNode,\n} from \"./processor/audio-mixer.js\";\nimport {\n type AzureTranslateNode,\n type AzureTranslateSettings,\n createAzureTranslateNode,\n} from \"./processor/azure-translate.js\";\nimport {\n type BufferNode,\n type BufferSettings,\n createBufferNode,\n} from \"./processor/buffer.js\";\nimport {\n type CambAiTranslateNode,\n type CambAiTranslateSettings,\n createCambAiTranslateNode,\n} from \"./processor/camb-ai-translate.js\";\nimport {\n type Cea608EncoderNode,\n type Cea608EncoderSettings,\n createCea608EncoderNode,\n} from \"./processor/cea608-encoder.js\";\nimport {\n createFfprobeMonitorNode,\n type FfprobeMonitorNode,\n type FfprobeMonitorSettings,\n} from \"./processor/ffprobe-monitor.js\";\nimport {\n createGateNode,\n type GateNode,\n type GateSettings,\n} from \"./processor/gate.js\";\nimport {\n createGeminiTranslateNode,\n type GeminiTranslateNode,\n type GeminiTranslateSettings,\n} from \"./processor/gemini-translate.js\";\nimport {\n createOpenAiTranslateNode,\n type OpenAiTranslateNode,\n type OpenAiTranslateSettings,\n} from \"./processor/openai-translate.js\";\n\nexport type { WorkflowInfo };\n\n/** Normalize a `catch`-clause/subscription value (typed `unknown`) into an `Error`. */\nfunction toError(err: unknown): Error {\n return err instanceof Error ? err : new Error(String(err));\n}\n\n/** Lifecycle callbacks available on a workflow. */\nexport interface WorkflowCallbacks {\n /**\n * Called if this workflow's worker process crashes. Workflow errors\n * otherwise only appear in server logs — this is the client-side signal.\n */\n onError?: (err: Error) => void;\n /** Called when the workflow closes, whether via `.close()` or a crash. */\n onClose?: () => void;\n}\n\n/** Events emitted by a workflow, mirroring {@link WorkflowCallbacks} for `.on()`-style consumers. */\nexport interface WorkflowEvents {\n error: (err: Error) => void;\n close: () => void;\n}\n\n/**\n * A workflow is an isolated sub-registry on the server.\n * All nodes created through a workflow handle are scoped to that workflow;\n * they cannot subscribe to nodes in other workflows.\n *\n * Obtain a workflow via `rema.workflow.create()`.\n */\nexport class Workflow extends TypedEmitter<WorkflowEvents> {\n readonly workflowId: string;\n /** Non-private: accessible to factory functions via WorkflowHandle. */\n readonly client: RemaClient;\n\n private readonly _callbacks: WorkflowCallbacks;\n private readonly _eventHandlers = new Map<\n string,\n Set<(evt: NodeEventMsg) => void>\n >();\n private _eventSub: { unsubscribe(): void } | null = null;\n private _stateSub: { unsubscribe(): void } | null = null;\n private _seenInList = false;\n private _erroredFired = false;\n private _closedFired = false;\n\n /** @internal — use `rema.workflow.create()` instead */\n constructor(\n workflowId: string,\n client: RemaClient,\n callbacks: WorkflowCallbacks = {},\n ) {\n super();\n this.workflowId = workflowId;\n this.client = client;\n this._callbacks = callbacks;\n this._startEventSubscription();\n this._startStateSubscription();\n }\n\n /**\n * Register a handler for events emitted by a specific node in this workflow.\n * Returns an unsubscribe function — call it when the node is closed.\n * @internal used by node factory functions\n */\n _onNodeEvent(\n nodeId: string,\n handler: (evt: NodeEventMsg) => void,\n ): () => void {\n let handlers = this._eventHandlers.get(nodeId);\n if (handlers === undefined) {\n handlers = new Set();\n this._eventHandlers.set(nodeId, handlers);\n }\n handlers.add(handler);\n return () => {\n this._eventHandlers.get(nodeId)?.delete(handler);\n };\n }\n\n private _startEventSubscription(): void {\n this._eventSub = this.client.trpc.workflow.events.subscribe(\n { workflowId: this.workflowId },\n {\n onData: (evt) => {\n this._eventHandlers.get(evt.nodeId)?.forEach((h) => {\n h(evt);\n });\n },\n onError: (err) => this._reportError(toError(err)),\n },\n );\n }\n\n /**\n * Watches the server's list of all workflows for this workflow's entry, so\n * `onError`/`onClose` can be derived without a dedicated per-workflow\n * subscription: a transition to `status: \"crashed\"` fires `onError`, and\n * the entry disappearing (graceful close) fires `onClose`.\n */\n private _startStateSubscription(): void {\n this._stateSub = this.client.trpc.workflow.state.subscribe(undefined, {\n onData: (workflows) => {\n const entry = workflows.find((w) => w.id === this.workflowId);\n if (entry !== undefined) {\n this._seenInList = true;\n if (entry.status === \"crashed\" && !this._erroredFired) {\n this._erroredFired = true;\n this._reportError(new Error(\"workflow worker crashed\"));\n }\n return;\n }\n if (this._seenInList) this._fireClose();\n },\n onError: (err) => this._reportError(toError(err)),\n });\n }\n\n /**\n * Report an error to this workflow's `onError` callback and its\n * `WorkflowEvents` emitter. Like `BaseNode`, `Workflow` extends Node's\n * `EventEmitter`, which throws synchronously from `emit()` when an\n * `\"error\"` event has no listener — so this only emits when someone has\n * opted in via `.on(\"error\", ...)`; the `onError` callback covers the\n * common case. The SDK does no logging of its own.\n */\n private _reportError(err: Error): void {\n this._callbacks.onError?.(err);\n if (this.listenerCount(\"error\") > 0) this.emit(\"error\", err);\n }\n\n private _fireClose(): void {\n if (this._closedFired) return;\n this._closedFired = true;\n this._eventSub?.unsubscribe();\n this._stateSub?.unsubscribe();\n this._callbacks.onClose?.();\n this.emit(\"close\");\n }\n\n // ─── Input nodes ─────────────────────────────────────────────────────────\n\n readonly input = {\n file: (\n settings: FileInputSettings,\n nodeId?: string,\n ): Promise<FileInputNode> => createFileInputNode(this, settings, nodeId),\n\n rtmpReader: (\n settings: RtmpReaderSettings,\n nodeId?: string,\n ): Promise<RtmpReaderNode> => createRtmpReaderNode(this, settings, nodeId),\n\n /** Announces RTMP publisher connections on an `app` without creating nodes. */\n rtmpAnnouncer: (\n settings: RtmpAnnouncerSettings,\n nodeId?: string,\n ): Promise<RtmpAnnouncerNode> =>\n createRtmpAnnouncerNode(this, settings, nodeId),\n\n /** Announces SRT publisher connections on an `app` without creating nodes. */\n srtAnnouncer: (\n settings: SrtAnnouncerSettings,\n nodeId?: string,\n ): Promise<SrtAnnouncerNode> =>\n createSrtAnnouncerNode(this, settings, nodeId),\n\n /** Reads an SRT stream ingested by MediaMTX, pulling it back via RTSP internally. */\n srtReader: (\n settings: SrtReaderSettings,\n nodeId?: string,\n ): Promise<SrtReaderNode> => createSrtReaderNode(this, settings, nodeId),\n\n /**\n * An RTMP multiplexer: accepts any number of simultaneous publishers under\n * the given `app`. For each connection `onStream` is called with a fresh\n * `RtmpStreamNode` that can be wired into its own encoder/output pipeline.\n */\n rtmpMultiplexer: (\n settings: RtmpMultiplexerSettings,\n nodeId?: string,\n ): Promise<RtmpMultiplexerNode> =>\n createRtmpMultiplexerNode(this, settings, nodeId),\n\n /** Format-agnostic caption schedule node. Send captions to it; wire it to a cea608() encoder. */\n captions: (\n settings: CaptionSourceSettings = {},\n nodeId?: string,\n ): Promise<CaptionSourceNode> =>\n createCaptionSourceNode(this, settings, nodeId),\n\n /**\n * Audio stream input node. Push a Node.js `Readable` (or any\n * `AsyncIterable<Buffer>`) directly into the pipeline by calling\n * `.stream(source)` on the returned node.\n */\n streamInput: (\n settings: NodeStreamInputSettings,\n nodeId?: string,\n ): Promise<NodeStreamInputNode> =>\n createNodeStreamInputNode(this, settings, nodeId),\n };\n\n // ─── Output nodes ─────────────────────────────────────────────────────────\n\n readonly output = {\n rtmp: (\n settings: RtmpOutputSettings,\n nodeId?: string,\n ): Promise<RtmpOutputNode> => createRtmpOutputNode(this, settings, nodeId),\n\n file: (\n settings: FileOutputSettings,\n nodeId?: string,\n ): Promise<FileOutputNode> => createFileOutputNode(this, settings, nodeId),\n\n /** HLS output node. Subscribe it to one or more media/subtitle sources. */\n hls: (\n settings: HlsOutputSettings,\n nodeId?: string,\n ): Promise<HlsOutputNode> => createHlsOutputNode(this, settings, nodeId),\n\n /** SRT output node. Pushes MPEG-TS over SRT to the given URL. */\n srt: (\n settings: SrtOutputSettings,\n nodeId?: string,\n ): Promise<SrtOutputNode> => createSrtOutputNode(this, settings, nodeId),\n };\n\n // ─── Processor nodes ──────────────────────────────────────────────────────\n\n readonly processor = {\n /** CEA-608/708 encoder node. Subscribe it to video + a captions() node. */\n cea608: (\n settings: Cea608EncoderSettings = {},\n nodeId?: string,\n ): Promise<Cea608EncoderNode> =>\n createCea608EncoderNode(this, settings, nodeId),\n\n /**\n * ffprobe monitor node.\n * Subscribe it to any media source; it periodically probes the live stream\n * and exposes codec/resolution/bitrate info via the `stream-info` monitor.\n */\n ffprobeMonitor: (\n settings: FfprobeMonitorSettings = {},\n nodeId?: string,\n ): Promise<FfprobeMonitorNode> =>\n createFfprobeMonitorNode(this, settings, nodeId),\n\n /**\n * Gemini Live Translate node.\n * Subscribe it to an audio/media source; it outputs translated audio-only\n * MPEG-TS using the Gemini Live Translate API.\n */\n geminiTranslate: (\n settings: GeminiTranslateSettings,\n nodeId?: string,\n ): Promise<GeminiTranslateNode> =>\n createGeminiTranslateNode(this, settings, nodeId),\n\n /**\n * Camb.ai realtime speech-to-speech translation node.\n * Subscribe it to an audio/media source; it outputs translated audio-only\n * MPEG-TS using the Camb.ai realtime translation API.\n */\n cambAiTranslate: (\n settings: CambAiTranslateSettings,\n nodeId?: string,\n ): Promise<CambAiTranslateNode> =>\n createCambAiTranslateNode(this, settings, nodeId),\n\n /**\n * OpenAI `gpt-realtime-translate` node.\n * Subscribe it to an audio/media source; it outputs translated audio-only\n * MPEG-TS using OpenAI's realtime translation API.\n */\n openAiTranslate: (\n settings: OpenAiTranslateSettings,\n nodeId?: string,\n ): Promise<OpenAiTranslateNode> =>\n createOpenAiTranslateNode(this, settings, nodeId),\n\n /**\n * Azure AI Speech real-time translation node.\n * Subscribe it to an audio/media source; it outputs translated audio-only\n * MPEG-TS using Azure Speech's real-time translation + synthesis API.\n */\n azureTranslate: (\n settings: AzureTranslateSettings,\n nodeId?: string,\n ): Promise<AzureTranslateNode> =>\n createAzureTranslateNode(this, settings, nodeId),\n\n /**\n * Time-shift node. Subscribe it to exactly one source; it re-emits that\n * source delayed by `bufferMs`, shared across every downstream\n * subscriber.\n */\n buffer: (settings: BufferSettings, nodeId?: string): Promise<BufferNode> =>\n createBufferNode(this, settings, nodeId),\n\n /**\n * Synchronization barrier for fan-in from multiple, independently-timed\n * sources. Subscribe it to two or more sources; it withholds all of them\n * until enough (`minReady`, default: all) have produced their first\n * item, then forwards everything live.\n */\n gate: (settings: GateSettings = {}, nodeId?: string): Promise<GateNode> =>\n createGateNode(this, settings, nodeId),\n\n /**\n * Audio mixer node. Subscribe it to exactly `settings.audio.length`\n * AUDIO/ALL sources, in order — the Nth `audio[]` entry configures the\n * Nth subscribed source. Outputs a single mixed audio feed.\n */\n audioMixer: (\n settings: AudioMixerSettings,\n nodeId?: string,\n ): Promise<AudioMixerNode> => createAudioMixerNode(this, settings, nodeId),\n };\n\n // ─── Lifecycle ────────────────────────────────────────────────────────────\n\n /**\n * Close this workflow and deactivate all its nodes on the server.\n * After calling this the workflow handle is no longer usable.\n *\n * The event/state subscriptions are kept open until the close mutation\n * resolves so that the server's per-node `CLOSED` events (dispatched to\n * each node's `onClose`/`onStatusChange` callbacks) are still received,\n * rather than tearing the subscriptions down before those events arrive.\n */\n async close(): Promise<void> {\n await this.client.trpc.workflow.close.mutate({\n workflowId: this.workflowId,\n });\n this._fireClose();\n }\n}\n","import { uid } from \"radash\";\nimport {\n BaseNode,\n dispatchNodeCallbacks,\n type NodeCallbacks,\n type WorkflowHandle,\n} from \"../node.js\";\n\n/** Per-source settings for one audio input to an `AudioMixerNode`. */\nexport interface AudioMixerInputSettings {\n /** Linear gain multiplier (1 = unity, 0.5 = -6dB). Default: 1. */\n volume?: number;\n /** Silences this input entirely, regardless of `volume`. Default: false. */\n muted?: boolean;\n /** Stereo balance: -1 (full left) .. 1 (full right), 0 = center. Default: 0. */\n pan?: number;\n /** High-pass filter cutoff (Hz), applied before mixing. Omit for no high-pass. */\n highpassHz?: number;\n /** Low-pass filter cutoff (Hz), applied before mixing. Omit for no low-pass. */\n lowpassHz?: number;\n}\n\nexport interface AudioMixerSettings extends NodeCallbacks {\n /**\n * One entry per audio source this node will be subscribed to, in the same\n * order — the Nth entry configures the Nth subscribed AUDIO/ALL source.\n * Subscribe with exactly `audio.length` sources.\n */\n audio: AudioMixerInputSettings[];\n}\n\nexport class AudioMixerNode extends BaseNode {\n readonly settings: AudioMixerSettings;\n\n constructor(\n nodeId: string,\n workflow: WorkflowHandle,\n settings: AudioMixerSettings,\n ) {\n super(nodeId, workflow.client, workflow.workflowId);\n this.settings = settings;\n }\n}\n\nexport async function createAudioMixerNode(\n workflow: WorkflowHandle,\n settings: AudioMixerSettings,\n nodeId?: string,\n): Promise<AudioMixerNode> {\n const id = nodeId ?? `audio_mixer_${uid(12)}`;\n const node = new AudioMixerNode(id, workflow, settings);\n\n node._bindCallbacks(settings);\n const removeHandler = workflow._onNodeEvent(id, (evt) => {\n dispatchNodeCallbacks(node, evt, settings);\n });\n node._setEventCleanup(removeHandler);\n\n await workflow.client.trpc.createNode\n .mutate({\n kind: \"AUDIO_MIXER\",\n workflowId: workflow.workflowId,\n nodeId: id,\n audioMixer: {\n audio: settings.audio.map((a) => ({\n volume: a.volume ?? 1,\n muted: a.muted ?? false,\n pan: a.pan ?? 0,\n ...(a.highpassHz !== undefined ? { highpassHz: a.highpassHz } : {}),\n ...(a.lowpassHz !== undefined ? { lowpassHz: a.lowpassHz } : {}),\n })),\n },\n })\n .catch((err: unknown) => {\n removeHandler();\n throw err;\n });\n\n return node;\n}\n","import type { HealthStatus, WorkflowInfo } from \"@remavideo/server\";\nimport { RemaClient, type RemaClientOptions } from \"./client.js\";\nimport { Workflow, type WorkflowCallbacks } from \"./workflow.js\";\n\nexport class Rema {\n private readonly client: RemaClient;\n private readonly _autoCloseWorkflows = new Set<Workflow>();\n\n private constructor(client: RemaClient) {\n this.client = client;\n }\n\n static async connect(opts: RemaClientOptions = {}): Promise<Rema> {\n const client = await RemaClient.connect(opts);\n return new Rema(client);\n }\n\n // ─── Workflow management ──────────────────────────────────────────────────\n\n readonly workflow = {\n /**\n * Create a new isolated workflow on the server.\n *\n * All nodes created through the returned `Workflow` handle are scoped to\n * this workflow; they cannot subscribe to nodes in other workflows.\n *\n * @param opts.name Human-readable label shown in the web UI.\n * @param opts.workflowId Optional stable ID. Server generates one if omitted.\n * @param opts.closeOnDisconnect When true, this workflow is closed automatically\n * when `rema.close()`/`wf.close()` is called, or\n * when the connection to the server is lost (e.g.\n * the process crashes) for more than a few seconds.\n * @param opts.parentId ID of an existing workflow to attach this one to as\n * a child (shown nested in the web UI).\n * @param opts.closeWithParent When true, this workflow is closed automatically\n * when its parent workflow is closed.\n * @param opts.onError Called if this workflow's worker process crashes.\n * @param opts.onClose Called when the workflow closes, whether via\n * `.close()` or a crash.\n */\n create: async (\n opts: {\n name?: string;\n workflowId?: string;\n closeOnDisconnect?: boolean;\n parentId?: string;\n closeWithParent?: boolean;\n } & WorkflowCallbacks = {},\n ): Promise<Workflow> => {\n const info = await this.client.trpc.workflow.create.mutate({\n workflowId: opts.workflowId,\n name: opts.name,\n parentId: opts.parentId,\n closeWithParent: opts.closeWithParent,\n closeOnDisconnect: opts.closeOnDisconnect,\n });\n const wf = new Workflow(info.id, this.client, {\n ...(opts.onError !== undefined && { onError: opts.onError }),\n ...(opts.onClose !== undefined && { onClose: opts.onClose }),\n });\n if (opts.closeOnDisconnect === true) {\n this._autoCloseWorkflows.add(wf);\n }\n return wf;\n },\n\n /** List all active workflows on the server. */\n list: (): Promise<WorkflowInfo[]> => this.client.trpc.workflow.list.query(),\n };\n\n /** Fetches the server's current health status (uptime, workflow counts, MediaMTX reachability). */\n health(): Promise<HealthStatus> {\n return this.client.health();\n }\n\n // ─── Lifecycle ────────────────────────────────────────────────────────────\n\n /**\n * Close all `closeOnDisconnect` workflows, then disconnect from the server.\n */\n async close(): Promise<void> {\n await Promise.allSettled(\n [...this._autoCloseWorkflows].map((wf) => wf.close()),\n );\n this.client.close();\n }\n}\n","/**\n * Stream selectors — used when calling node.subscribe() to pick which\n * streams flow from source to subscriber.\n */\n\nexport type StreamSelector = \"VIDEO\" | \"AUDIO\" | \"CAPTIONS\" | \"ALL\";\n\n// Narrowly typed (`as const`) so a spec literal like\n// `{ sourceSelector: selectAudio, audio: {...} }` discriminates the\n// SubscriptionSpec union correctly.\nexport const selectVideo = \"VIDEO\" as const;\nexport const selectAudio = \"AUDIO\" as const;\nexport const selectCaptions = \"CAPTIONS\" as const;\nexport const selectAll = \"ALL\" as const;\n"],"mappings":"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","names":["uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","uid","TypedEmitter","uid","uid","toError","TypedEmitter"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remavideo/sdk",
3
- "version": "0.16.2",
3
+ "version": "0.17.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -32,7 +32,7 @@
32
32
  "tsx": "^4.21.0",
33
33
  "typescript": "^5.8.3",
34
34
  "vitest": "^3.0.0",
35
- "@remavideo/server": "0.16.2"
35
+ "@remavideo/server": "0.17.0"
36
36
  },
37
37
  "scripts": {
38
38
  "build": "tsup",