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({