diff --git a/apps/backend/setup.json b/apps/backend/setup.json index 3623c1be..97223e9e 100644 --- a/apps/backend/setup.json +++ b/apps/backend/setup.json @@ -1,5 +1,5 @@ { - "hw": "jetson", + "hw": "rk3588", "ceracoder_path": "./mocks/ceracoder/", "ceracoder_config": "/tmp/ceracoder.conf", "srtla_path": "./mocks/srtla/", diff --git a/apps/backend/src/modules/streaming/pipelines.ts b/apps/backend/src/modules/streaming/pipelines.ts index 59ddd84d..a18f6981 100644 --- a/apps/backend/src/modules/streaming/pipelines.ts +++ b/apps/backend/src/modules/streaming/pipelines.ts @@ -120,7 +120,13 @@ export function searchPipelines(id: string): Pipeline | null { // Pipeline list in the format needed by the frontend type PipelineResponseEntry = Pick< Pipeline, - "name" | "description" | "supportsAudio" | "supportsResolutionOverride" | "supportsFramerateOverride" + | "name" + | "description" + | "supportsAudio" + | "supportsResolutionOverride" + | "supportsFramerateOverride" + | "defaultResolution" + | "defaultFramerate" >; export function getPipelineList() { @@ -135,11 +141,23 @@ export function getPipelineList() { supportsAudio: pipeline.supportsAudio, supportsResolutionOverride: pipeline.supportsResolutionOverride, supportsFramerateOverride: pipeline.supportsFramerateOverride, + defaultResolution: pipeline.defaultResolution, + defaultFramerate: pipeline.defaultFramerate, }; } return list; } +/** + * Get pipelines message with hardware info (for WebSocket broadcast) + */ +export function getPipelinesMessage() { + return { + hardware: getEffectiveHardware(), + pipelines: getPipelineList(), + }; +} + /** * Generate a pipeline file with overrides */ diff --git a/apps/backend/src/modules/ui/status.ts b/apps/backend/src/modules/ui/status.ts index 5c3b4187..c55d96f5 100644 --- a/apps/backend/src/modules/ui/status.ts +++ b/apps/backend/src/modules/ui/status.ts @@ -23,7 +23,7 @@ import { netIfBuildMsg } from "../network/network-interfaces.ts"; import { buildRelaysMsg, getRelays } from "../remote/remote-relays.ts"; import { getAudioDevices } from "../streaming/audio.ts"; import { AUDIO_CODECS } from "@ceralive/ceracoder"; -import { getPipelineList } from "../streaming/pipelines.ts"; +import { getPipelinesMessage } from "../streaming/pipelines.ts"; import { getIsStreaming } from "../streaming/streaming.ts"; import { getRevisions } from "../system/revisions.ts"; import { getSensors } from "../system/sensors.ts"; @@ -65,7 +65,7 @@ export function sendStatus(conn: WebSocket) { export function sendInitialStatus(conn: WebSocket) { const config = getConfig(); conn.send(buildMsg("config", config)); - conn.send(buildMsg("pipelines", getPipelineList())); + conn.send(buildMsg("pipelines", getPipelinesMessage())); if (getRelays()) conn.send(buildMsg("relays", buildRelaysMsg())); sendStatus(conn); conn.send(buildMsg("netif", netIfBuildMsg())); diff --git a/apps/backend/src/rpc/procedures/status.procedure.ts b/apps/backend/src/rpc/procedures/status.procedure.ts index 00e58307..67db4c13 100644 --- a/apps/backend/src/rpc/procedures/status.procedure.ts +++ b/apps/backend/src/rpc/procedures/status.procedure.ts @@ -15,7 +15,7 @@ import { } from "../../modules/remote/remote-relays.ts"; import { getAudioDevices } from "../../modules/streaming/audio.ts"; import { AUDIO_CODECS } from "@ceralive/ceracoder"; -import { getPipelineList } from "../../modules/streaming/pipelines.ts"; +import { getPipelinesMessage } from "../../modules/streaming/pipelines.ts"; import { getIsStreaming } from "../../modules/streaming/streaming.ts"; import { getRevisions } from "../../modules/system/revisions.ts"; import { getSensors } from "../../modules/system/sensors.ts"; @@ -71,7 +71,7 @@ export function buildInitialStatus() { const config = getConfig(); return { config, - pipelines: getPipelineList(), + pipelines: getPipelinesMessage(), relays: getRelays() ? buildRelaysMsg() : null, status: { is_streaming: getIsStreaming(), diff --git a/apps/backend/src/rpc/procedures/streaming.procedure.ts b/apps/backend/src/rpc/procedures/streaming.procedure.ts index 6b7c9ebb..3a482238 100644 --- a/apps/backend/src/rpc/procedures/streaming.procedure.ts +++ b/apps/backend/src/rpc/procedures/streaming.procedure.ts @@ -9,7 +9,7 @@ import { bitrateOutputSchema, configMessageSchema, getMockHardwareOutputSchema, - pipelinesSchema, + pipelinesMessageSchema, setMockHardwareInputSchema, setMockHardwareOutputSchema, streamingConfigInputSchema, @@ -25,6 +25,7 @@ import { getEffectiveHardware, getMockHardware, getPipelineList, + getPipelinesMessage, initPipelines, setMockHardware, VALID_HARDWARE_TYPES, @@ -88,12 +89,15 @@ export const setBitrateProcedure = authedProcedure }); /** - * Get pipelines procedure + * Get pipelines procedure - returns pipelines with hardware info */ export const getPipelinesProcedure = authedProcedure - .output(pipelinesSchema) + .output(pipelinesMessageSchema) .handler(() => { - return getPipelineList(); + return { + hardware: getEffectiveHardware(), + pipelines: getPipelineList(), + }; }); /** @@ -149,7 +153,7 @@ export const setMockHardwareProcedure = authedProcedure if (success) { // Reload pipelines and broadcast to all clients initPipelines(); - broadcastMsg("pipelines", getPipelineList()); + broadcastMsg("pipelines", getPipelinesMessage()); return { success: true, hardware: input.hardware, diff --git a/apps/frontend/docs/DEVTOOLS.md b/apps/frontend/docs/DEVTOOLS.md index df757f8c..6f39a75a 100644 --- a/apps/frontend/docs/DEVTOOLS.md +++ b/apps/frontend/docs/DEVTOOLS.md @@ -63,6 +63,21 @@ The DevTools tab displays real-time information about the current development en - Test toast queue management - **Usage**: Click various buttons to trigger different toast notifications +#### 🔧 Mock Hardware Switcher +- **Purpose**: Switch between hardware profiles to test different pipeline configurations +- **Features**: + - **Hardware Profiles**: Switch between Jetson, RK3588, N100, and Generic (software) + - **Live Reload**: Pipelines update immediately and broadcast to all connected clients + - **Quick Switch Buttons**: Color-coded buttons for rapid hardware switching + - **Dropdown Selector**: Detailed view with hardware descriptions + - **State Display**: Shows effective hardware and current mock override +- **Hardware Types**: + - 🟢 **NVIDIA Jetson**: NVIDIA nvenc hardware encoding + - 🟠 **Rockchip RK3588**: Rockchip MPP hardware encoding (supports 4K) + - 🔵 **Intel N100**: Intel VAAPI hardware encoding + - ⚪ **Generic**: Software x264/x265 encoding +- **Usage**: Select a hardware profile to reload pipelines for that platform. The encoder settings will update to show available video sources for the selected hardware. + ### 📊 System Information Real-time system and browser information display: diff --git a/apps/frontend/src/lib/components/dev-tools/hardware-switcher.svelte b/apps/frontend/src/lib/components/dev-tools/hardware-switcher.svelte index 7f9572ae..130d5b26 100644 --- a/apps/frontend/src/lib/components/dev-tools/hardware-switcher.svelte +++ b/apps/frontend/src/lib/components/dev-tools/hardware-switcher.svelte @@ -1,51 +1,55 @@ @@ -446,20 +306,17 @@ const handleFramerateChange = (value: string) => {
(properties.bitrate = value)} onBitrateOverlayChange={(checked) => (properties.bitrateOverlay = checked)} - onEncoderChange={handleEncoderChange} - onFramerateChange={handleFramerateChange} - onInputModeChange={handleInputModeChange} + onSourceChange={handleSourceChange} onResolutionChange={handleResolutionChange} + onFramerateChange={handleFramerateChange} properties={{ - inputMode: properties.inputMode, - encoder: properties.encoder, + source: properties.source, resolution: properties.resolution, framerate: properties.framerate, bitrate: properties.bitrate, @@ -486,7 +343,7 @@ const handleFramerateChange = (value: string) => { audioCodec: properties.audioCodec, audioDelay: properties.audioDelay, }} - {unparsedPipelines} + pipelines={pipelines} />
@@ -536,24 +393,21 @@ const handleFramerateChange = (value: string) => { - + { - // Try to dismiss all toasts first try { toast.dismiss(); } catch (error) { console.warn('Could not dismiss toasts:', error); } - // Stop streaming with proper toast cleanup via the global function if (window.stopStreamingWithNotificationClear) { window.stopStreamingWithNotificationClear(); } else { - // Fallback stopStreaming(); } }} diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 00000000..bb32b835 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,88 @@ +# CeraUI Architecture + +## Components +- **Frontend (Svelte)**: Browser UI, talks to backend via WebSocket/RPC. +- **Backend (Bun/TypeScript)**: Orchestrates ceracoder, srtla, network/modem, relays RPC events. +- **ceracoder**: Runs GStreamer pipeline internally (capture, encode, mux) and sends SRT. +- **srtla**: Splits SRT packets across multiple interfaces (bonding). +- **srtla_rec**: Reassembles bonded streams on the server. +- **srt-live-transmit**: Relays to the final consumer (OBS/Player/CDN). + +## System Flow (Frontend → Backend → Encoder) +``` +Browser (CeraUI Frontend) + | + | WebSocket / RPC + v +Backend (Bun/TypeScript) + | + | spawn / signal / configure + v +ceracoder + | + | SRT (localhost) + v +srtla (sender) + | + +-- Modem1 (4G/5G) + +-- Modem2 (4G/5G) + +-- WiFi / Ethernet + | + v + Internet +``` + +## Streaming Pipeline (Encoder → Bonding → Server) +``` +ENCODER DEVICE (Field) SERVER (Ingest/Cloud) +====================== ===================== + +Video Source (HDMI/USB/SRT/etc) + | + v + ceracoder + +---------------------------+ + | GStreamer (internal) | + | - Capture/Encode/Mux | + | - MPEG-TS + SRT send | + +-------------+-------------+ + | + | SRT (localhost:9000) + v + srtla (sender) + +-------------+-------------+ + | splits packets across | + | multiple interfaces | + +------+------+------+------+ + | | | + Modem1 Modem2 WiFi + \ | / + \ | / + \ | / + \ | / + \ | / + \ | / + Internet + | + v + srtla_rec + +----------------------+ + | reassemble bonded | + | SRT stream | + +----------+-----------+ + | + v + srt-live-transmit + +----------------------+ + | relay / bridge | + +----------+-----------+ + | + v + OBS / Player / CDN +``` + +## Data & Control Paths +- **Control**: Frontend → Backend (RPC) → ceracoder/srtla (spawn, config, SIGHUP). +- **Media**: ceracoder (GStreamer) → SRT → srtla → bonded links → srtla_rec → srt-live-transmit → consumer. +- **Bitrate Adaptation**: ceracoder reads SRT stats, adjusts encoder bitrate dynamically. +- **Config Reload**: ceracoder supports SIGHUP to reload INI without restart. diff --git a/packages/i18n/src/ar/index.ts b/packages/i18n/src/ar/index.ts index a842ee19..4b14f779 100644 --- a/packages/i18n/src/ar/index.ts +++ b/packages/i18n/src/ar/index.ts @@ -259,6 +259,24 @@ const ar = { settings: { encoderSettings: "إعدادات المرمز", inputMode: "وضع الإدخال", + hardware: "الأجهزة", + hardwareTypes: { + jetson: "NVIDIA Jetson", + rk3588: "Rockchip RK3588", + n100: "Intel N100", + generic: "Generic (Software)", + }, + sources: { + camlink: "Cam Link 4K", + libuvch264: "كاميرا UVC H264", + hdmi: "التقاط HDMI", + usb_mjpeg: "USB MJPEG", + v4l_mjpeg: "V4L2 MJPEG", + rtmp: "إدخال RTMP", + srt: "إدخال SRT", + test: "نمط اختبار", + decklink: "Decklink SDI", + }, djiCameraMessage: "قد تعمل كاميرات DJI بشكل أفضل باستخدام وضع الإدخال USB-LIBUVCH264", selectInputMode: "اختر وضع الإدخال", diff --git a/packages/i18n/src/de/index.ts b/packages/i18n/src/de/index.ts index e3d417a2..03221b37 100644 --- a/packages/i18n/src/de/index.ts +++ b/packages/i18n/src/de/index.ts @@ -287,6 +287,24 @@ const de = { settings: { encoderSettings: "Encoder-Einstellungen", inputMode: "Eingabemodus", + hardware: "Hardware", + hardwareTypes: { + jetson: "NVIDIA Jetson", + rk3588: "Rockchip RK3588", + n100: "Intel N100", + generic: "Generic (Software)", + }, + sources: { + camlink: "Cam Link 4K", + libuvch264: "UVC H264 Kamera", + hdmi: "HDMI Capture", + usb_mjpeg: "USB MJPEG", + v4l_mjpeg: "V4L2 MJPEG", + rtmp: "RTMP Ingest", + srt: "SRT Ingest", + test: "Testbild", + decklink: "Decklink SDI", + }, djiCameraMessage: "DJI-Kameras funktionieren möglicherweise besser im USB-LIBUVCH264-Eingabemodus", selectInputMode: "Wähle den Eingabemodus", diff --git a/packages/i18n/src/en/index.ts b/packages/i18n/src/en/index.ts index 119b0bd0..df5dc4e9 100644 --- a/packages/i18n/src/en/index.ts +++ b/packages/i18n/src/en/index.ts @@ -289,6 +289,26 @@ const en = { settings: { encoderSettings: "Encoder Settings", inputMode: "Input Mode", + hardware: "Hardware", + // Hardware types + hardwareTypes: { + jetson: "NVIDIA Jetson", + rk3588: "Rockchip RK3588", + n100: "Intel N100", + generic: "Generic (Software)", + }, + // Video sources + sources: { + camlink: "Cam Link 4K", + libuvch264: "UVC H264 Camera", + hdmi: "HDMI Capture", + usb_mjpeg: "USB MJPEG", + v4l_mjpeg: "V4L2 MJPEG", + rtmp: "RTMP Ingest", + srt: "SRT Ingest", + test: "Test Pattern", + decklink: "Decklink SDI", + }, djiCameraMessage: "DJI cameras may work better using the USB-LIBUVCH264 input mode", selectInputMode: "Select the input mode", diff --git a/packages/i18n/src/es/index.ts b/packages/i18n/src/es/index.ts index 65baaad7..24c239b3 100644 --- a/packages/i18n/src/es/index.ts +++ b/packages/i18n/src/es/index.ts @@ -299,6 +299,26 @@ const es = { settings: { encoderSettings: "Configuración del codificador", inputMode: "Modo de entrada", + hardware: "Hardware", + // Hardware types + hardwareTypes: { + jetson: "NVIDIA Jetson", + rk3588: "Rockchip RK3588", + n100: "Intel N100", + generic: "Genérico (Software)", + }, + // Video sources + sources: { + camlink: "Cam Link 4K", + libuvch264: "Cámara UVC H264", + hdmi: "Captura HDMI", + usb_mjpeg: "USB MJPEG", + v4l_mjpeg: "V4L2 MJPEG", + rtmp: "Entrada RTMP", + srt: "Entrada SRT", + test: "Patrón de prueba", + decklink: "Decklink SDI", + }, djiCameraMessage: "Las cámaras DJI suelen funcionar mejor usando el modo de entrada USB-LIBUVCH264", selectInputMode: "Seleccione el modo de Entrada", diff --git a/packages/i18n/src/fr/index.ts b/packages/i18n/src/fr/index.ts index a36e79e5..148a489b 100644 --- a/packages/i18n/src/fr/index.ts +++ b/packages/i18n/src/fr/index.ts @@ -273,6 +273,24 @@ const fr = { settings: { encoderSettings: "Paramètres de l'encodeur", inputMode: "Mode d'entrée", + hardware: "Matériel", + hardwareTypes: { + jetson: "NVIDIA Jetson", + rk3588: "Rockchip RK3588", + n100: "Intel N100", + generic: "Generic (Software)", + }, + sources: { + camlink: "Cam Link 4K", + libuvch264: "Caméra UVC H264", + hdmi: "Capture HDMI", + usb_mjpeg: "USB MJPEG", + v4l_mjpeg: "V4L2 MJPEG", + rtmp: "Entrée RTMP", + srt: "Entrée SRT", + test: "Mire de test", + decklink: "Decklink SDI", + }, djiCameraMessage: "Les caméras DJI peuvent mieux fonctionner en utilisant le mode d’entrée USB-LIBUVCH264", selectInputMode: "Sélectionnez le mode d'entrée", diff --git a/packages/i18n/src/hi/index.ts b/packages/i18n/src/hi/index.ts index 4bf69c5f..773e31fd 100644 --- a/packages/i18n/src/hi/index.ts +++ b/packages/i18n/src/hi/index.ts @@ -85,6 +85,24 @@ const hi = { settings: { encoderSettings: "एनकोडर सेटिंग्स", inputMode: "इनपुट मोड", + hardware: "हार्डवेयर", + hardwareTypes: { + jetson: "NVIDIA Jetson", + rk3588: "Rockchip RK3588", + n100: "Intel N100", + generic: "Generic (Software)", + }, + sources: { + camlink: "Cam Link 4K", + libuvch264: "UVC H264 कैमरा", + hdmi: "HDMI कैप्चर", + usb_mjpeg: "USB MJPEG", + v4l_mjpeg: "V4L2 MJPEG", + rtmp: "RTMP इनजेस्ट", + srt: "SRT इनजेस्ट", + test: "टेस्ट पैटर्न", + decklink: "Decklink SDI", + }, selectInputMode: "इनपुट मोड चुनें", djiCameraMessage: "DJI कैमरे USB-LIBUVCH264 इनपुट मोड का उपयोग करके बेहतर काम कर सकते हैं", diff --git a/packages/i18n/src/ja/index.ts b/packages/i18n/src/ja/index.ts index ab058540..facdbdb3 100644 --- a/packages/i18n/src/ja/index.ts +++ b/packages/i18n/src/ja/index.ts @@ -86,6 +86,24 @@ const ja = { settings: { encoderSettings: "エンコーダー設定", inputMode: "入力モード", + hardware: "ハードウェア", + hardwareTypes: { + jetson: "NVIDIA Jetson", + rk3588: "Rockchip RK3588", + n100: "Intel N100", + generic: "Generic (Software)", + }, + sources: { + camlink: "Cam Link 4K", + libuvch264: "UVC H264 カメラ", + hdmi: "HDMI キャプチャ", + usb_mjpeg: "USB MJPEG", + v4l_mjpeg: "V4L2 MJPEG", + rtmp: "RTMP インジェスト", + srt: "SRT インジェスト", + test: "テストパターン", + decklink: "Decklink SDI", + }, djiCameraMessage: "DJIカメラはUSB-LIBUVCH264入力モードを使用するとより良く動作する可能性があります", selectInputMode: "入力モードを選択", diff --git a/packages/i18n/src/ko/index.ts b/packages/i18n/src/ko/index.ts index cacc62ea..3fe77098 100644 --- a/packages/i18n/src/ko/index.ts +++ b/packages/i18n/src/ko/index.ts @@ -85,6 +85,24 @@ const ko = { settings: { encoderSettings: "인코더 설정", inputMode: "입력 모드", + hardware: "하드웨어", + hardwareTypes: { + jetson: "NVIDIA Jetson", + rk3588: "Rockchip RK3588", + n100: "Intel N100", + generic: "Generic (Software)", + }, + sources: { + camlink: "Cam Link 4K", + libuvch264: "UVC H264 카메라", + hdmi: "HDMI 캡처", + usb_mjpeg: "USB MJPEG", + v4l_mjpeg: "V4L2 MJPEG", + rtmp: "RTMP 인제스트", + srt: "SRT 인제스트", + test: "테스트 패턴", + decklink: "Decklink SDI", + }, djiCameraMessage: "DJI 카메라는 USB-LIBUVCH264 입력 모드를 사용하면 더 잘 작동할 수 있습니다", selectInputMode: "입력 모드를 선택", diff --git a/packages/i18n/src/pt-BR/index.ts b/packages/i18n/src/pt-BR/index.ts index b561932e..9700f04a 100644 --- a/packages/i18n/src/pt-BR/index.ts +++ b/packages/i18n/src/pt-BR/index.ts @@ -272,6 +272,24 @@ const ptBR = { settings: { encoderSettings: "Configurações do Codificador", inputMode: "Modo de Entrada", + hardware: "Hardware", + hardwareTypes: { + jetson: "NVIDIA Jetson", + rk3588: "Rockchip RK3588", + n100: "Intel N100", + generic: "Generic (Software)", + }, + sources: { + camlink: "Cam Link 4K", + libuvch264: "Câmera UVC H264", + hdmi: "Captura HDMI", + usb_mjpeg: "USB MJPEG", + v4l_mjpeg: "V4L2 MJPEG", + rtmp: "Ingestão RTMP", + srt: "Ingestão SRT", + test: "Padrão de teste", + decklink: "Decklink SDI", + }, djiCameraMessage: "As câmeras DJI podem funcionar melhor usando o modo de entrada USB-LIBUVCH264", selectInputMode: "Selecione o modo de entrada", diff --git a/packages/i18n/src/zh/index.ts b/packages/i18n/src/zh/index.ts index e71dd7a9..ca60dd6c 100644 --- a/packages/i18n/src/zh/index.ts +++ b/packages/i18n/src/zh/index.ts @@ -84,6 +84,24 @@ const zh = { settings: { encoderSettings: "编码器设置", inputMode: "输入模式", + hardware: "硬件", + hardwareTypes: { + jetson: "NVIDIA Jetson", + rk3588: "Rockchip RK3588", + n100: "Intel N100", + generic: "Generic (Software)", + }, + sources: { + camlink: "Cam Link 4K", + libuvch264: "UVC H264 相机", + hdmi: "HDMI 采集", + usb_mjpeg: "USB MJPEG", + v4l_mjpeg: "V4L2 MJPEG", + rtmp: "RTMP 采集", + srt: "SRT 采集", + test: "测试图案", + decklink: "Decklink SDI", + }, djiCameraMessage: "DJI 相机在使用 USB-LIBUVCH264 输入模式时可能表现更好", selectInputMode: "选择输入模式", selectEncodingOutputFormat: "选择输出编解码器", diff --git a/packages/rpc/src/schemas/streaming.schema.ts b/packages/rpc/src/schemas/streaming.schema.ts index 9b4e585f..24dc6231 100644 --- a/packages/rpc/src/schemas/streaming.schema.ts +++ b/packages/rpc/src/schemas/streaming.schema.ts @@ -40,20 +40,113 @@ export const bitrateOutputSchema = z.object({ }); export type BitrateOutput = z.infer; -// Pipeline schema +// Resolution and framerate types for pipeline overrides +export const resolutionSchema = z.enum(["480p", "720p", "1080p", "1440p", "2160p", "4k"]); +export type Resolution = z.infer; + +export const framerateSchema = z.union([ + z.literal(25), + z.literal(29.97), + z.literal(30), + z.literal(50), + z.literal(59.94), + z.literal(60), +]); +export type Framerate = z.infer; + +// Hardware type schema +export const hardwareTypeSchema = z.enum(["jetson", "rk3588", "n100", "generic"]); +export type HardwareType = z.infer; + +// Hardware labels (browser-safe, no Node deps) +export const HARDWARE_LABELS: Record = { + jetson: "NVIDIA Jetson", + rk3588: "Rockchip RK3588", + n100: "Intel N100", + generic: "Generic (Software)", +}; + +// Hardware descriptions for UI +export const HARDWARE_DESCRIPTIONS: Record = { + jetson: "NVIDIA nvenc hardware encoding", + rk3588: "Rockchip MPP hardware encoding (supports 4K)", + n100: "Intel VAAPI hardware encoding", + generic: "Software x264/x265 encoding", +}; + +// Hardware colors for UI (Tailwind classes) +export const HARDWARE_COLORS: Record = { + jetson: { + text: "text-green-600 dark:text-green-400", + bg: "bg-green-500", + border: "border-green-500 bg-green-500/20", + }, + rk3588: { + text: "text-orange-600 dark:text-orange-400", + bg: "bg-orange-500", + border: "border-orange-500 bg-orange-500/20", + }, + n100: { + text: "text-blue-600 dark:text-blue-400", + bg: "bg-blue-500", + border: "border-blue-500 bg-blue-500/20", + }, + generic: { + text: "text-gray-600 dark:text-gray-400", + bg: "bg-gray-500", + border: "border-gray-500 bg-gray-500/20", + }, +}; + +// Video source labels (browser-safe) +export const VIDEO_SOURCE_LABELS: Record = { + camlink: "Cam Link 4K", + libuvch264: "UVC H264 Camera", + hdmi: "HDMI Capture", + usb_mjpeg: "USB MJPEG", + v4l_mjpeg: "V4L2 MJPEG", + rtmp: "RTMP Ingest", + srt: "SRT Ingest", + test: "Test Pattern", + decklink: "Decklink SDI", +}; + +// Pipeline schema - now based on video sources with structured metadata export const pipelineSchema = z.object({ name: z.string(), - asrc: z.boolean(), - acodec: z.boolean(), + description: z.string(), + supportsAudio: z.boolean(), + supportsResolutionOverride: z.boolean(), + supportsFramerateOverride: z.boolean(), + defaultResolution: resolutionSchema.optional(), + defaultFramerate: framerateSchema.optional(), }); export type Pipeline = z.infer; -// Pipelines message schema +// Pipelines message schema - includes hardware info +export const pipelinesMessageSchema = z.object({ + hardware: hardwareTypeSchema, + pipelines: z.record(z.string(), pipelineSchema), +}); +export type PipelinesMessage = z.infer; + +// Legacy alias for backwards compatibility export const pipelinesSchema = z.record(z.string(), pipelineSchema); export type Pipelines = z.infer; -// Audio codecs message schema -export const audioCodecsMessageSchema = z.record(audioCodecSchema, z.string()); +// Available resolutions for UI +export const AVAILABLE_RESOLUTIONS: Resolution[] = ["480p", "720p", "1080p", "1440p", "2160p"]; + +// Available framerates for UI +export const AVAILABLE_FRAMERATES: Framerate[] = [25, 29.97, 30, 50, 59.94, 60]; + +// Audio codecs message schema (objects with name field) +export const audioCodecsMessageSchema = z.record( + audioCodecSchema, + z.object({ + name: z.string(), + }), +); export type AudioCodecsMessage = z.infer; import { customProviderInputSchema, providerSelectionSchema } from './cloud-provider.schema'; @@ -92,8 +185,8 @@ export const streamingStopOutputSchema = z.object({ }); export type StreamingStopOutput = z.infer; -// Dev-only mock hardware switcher schemas -export const mockHardwareTypeSchema = z.enum(['jetson', 'n100', 'rk3588']); +// Dev-only mock hardware switcher schemas (includes generic for software fallback) +export const mockHardwareTypeSchema = z.enum(['jetson', 'n100', 'rk3588', 'generic']); export type MockHardwareType = z.infer; export const setMockHardwareInputSchema = z.object({