From 0a5b4d6191b0efa418d1e4e93d70f56e78c0677b Mon Sep 17 00:00:00 2001 From: ZGaetano Date: Sat, 16 May 2026 08:23:24 -0400 Subject: [PATCH] feat(ui): SRT/RTMP listener/caller mode UI in recorders - SRT: mode selector (Listener / Caller) - Listener: listen_port field + live connection info banner - Caller: source URL field - RTMP: mode selector (Listener / Caller) - Listener: listen_port + stream_key fields + live connection info banner - Caller: source URL field - Connection info banners update live as port/key fields change - handleCreateRecorder builds correct source_config per mode - Card meta display handles listener config (shows port, not url) - updateSrtModeFields / updateRtmpModeFields helpers for dynamic show/hide --- services/web-ui/public/recorders.html | 198 ++++++++++++++++++++++++-- 1 file changed, 184 insertions(+), 14 deletions(-) diff --git a/services/web-ui/public/recorders.html b/services/web-ui/public/recorders.html index 90307e7..f584dc1 100644 --- a/services/web-ui/public/recorders.html +++ b/services/web-ui/public/recorders.html @@ -192,6 +192,10 @@ color: var(--color-text-primary); font-weight: 500; font-family: 'Courier New', monospace; + font-size: 0.8rem; + word-break: break-all; + text-align: right; + max-width: 60%; } .recorder-duration { @@ -402,6 +406,38 @@ display: flex; } + .connection-info { + background-color: var(--color-bg-primary); + border-radius: var(--radius-md); + border: 1px solid var(--color-border); + padding: var(--spacing-md); + display: flex; + flex-direction: column; + gap: var(--spacing-xs); + } + + .connection-info-label { + font-size: 0.8rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.3px; + } + + .connection-info-label.srt { + color: var(--color-warning); + } + + .connection-info-label.rtmp { + color: var(--color-success); + } + + .connection-info-url { + font-family: 'Courier New', monospace; + font-size: 0.85rem; + color: var(--color-text-secondary); + word-break: break-all; + } + @media (max-width: 768px) { .recorders-grid { grid-template-columns: 1fr; @@ -487,7 +523,7 @@ - +
@@ -657,6 +693,15 @@ updateStatusBar(); } + function getSourceDisplay(recorder) { + const cfg = recorder.source_config || {}; + if (cfg.mode === 'listener') { + const port = cfg.listen_port || (recorder.source_type === 'srt' ? 9000 : 1935); + return `Listen :${port}`; + } + return cfg.url || cfg.device || '—'; + } + function createRecorderCard(recorder) { const isRecording = recorder.status === 'recording'; const card = document.createElement('div'); @@ -664,6 +709,7 @@ const sourceTypeLower = (recorder.source_type || '').toLowerCase(); const sourceTypeClass = ['sdi', 'srt', 'rtmp'].includes(sourceTypeLower) ? sourceTypeLower : 'sdi'; + const sourceDisplay = getSourceDisplay(recorder); card.innerHTML = `
@@ -682,7 +728,7 @@
Source - ${recorder.source_config?.url || recorder.source_config?.device || '—'} + ${sourceDisplay}
Codec @@ -724,6 +770,7 @@ document.getElementById('recordingCodec').value = 'prores_hq'; document.getElementById('resolution').value = 'native'; document.getElementById('proxyToggle').classList.remove('on'); + document.getElementById('proxyFields').classList.remove('visible'); recorderState.proxyEnabled = false; updateSourceFields(); } @@ -732,6 +779,11 @@ document.getElementById('createModal').classList.remove('active'); } + // ============================================================ + // SOURCE FIELD BUILDERS + // Rebuilt each time the source type selector changes. + // ============================================================ + function updateSourceFields() { const sourceType = document.getElementById('sourceType').value; const fieldsContainer = document.getElementById('sourceConfigFields'); @@ -749,34 +801,125 @@
`; fieldsContainer.classList.add('visible'); + } else if (sourceType === 'srt') { fieldsContainer.innerHTML = ` -
- - -
- + +
+ + +
+
+ + +
+
+
📡 Push SRT to this address
+
+ srt://<server-ip>:9000?mode=caller +
+
+
+ + + `; fieldsContainer.classList.add('visible'); + } else if (sourceType === 'rtmp') { fieldsContainer.innerHTML = `
- - + + +
+ + +
+
+ + +
+
+ + +
+
+
📡 Push RTMP to this address
+
+ rtmp://<server-ip>:1935/live/stream +
+
+
+ + + `; fieldsContainer.classList.add('visible'); + } else { fieldsContainer.classList.remove('visible'); } } + // Update SRT fields when mode selector changes + function updateSrtModeFields() { + const mode = document.getElementById('srtMode').value; + document.getElementById('srtListenerFields').style.display = + mode === 'listener' ? 'flex' : 'none'; + document.getElementById('srtCallerFields').style.display = + mode === 'caller' ? 'flex' : 'none'; + } + + // Refresh SRT connection info as port field changes + function refreshSrtInfo() { + const port = document.getElementById('srtListenPort').value || '9000'; + const el = document.getElementById('srtConnectionInfo'); + if (el) el.textContent = `srt://:${port}?mode=caller`; + } + + // Update RTMP fields when mode selector changes + function updateRtmpModeFields() { + const mode = document.getElementById('rtmpMode').value; + document.getElementById('rtmpListenerFields').style.display = + mode === 'listener' ? 'flex' : 'none'; + document.getElementById('rtmpCallerFields').style.display = + mode === 'caller' ? 'flex' : 'none'; + } + + // Refresh RTMP connection info as port/key fields change + function refreshRtmpInfo() { + const port = document.getElementById('rtmpListenPort').value || '1935'; + const key = document.getElementById('rtmpStreamKey').value || 'stream'; + const el = document.getElementById('rtmpConnectionInfo'); + if (el) el.textContent = `rtmp://:${port}/live/${key}`; + } + function toggleProxy() { const toggle = document.getElementById('proxyToggle'); const fields = document.getElementById('proxyFields'); @@ -810,11 +953,38 @@ if (sourceType === 'sdi') { sourceConfig.device = document.getElementById('sdiDevice').value; + } else if (sourceType === 'srt') { - sourceConfig.url = document.getElementById('srtUrl').value; - sourceConfig.mode = document.getElementById('srtMode').value; + const mode = document.getElementById('srtMode').value; + sourceConfig.mode = mode; + if (mode === 'listener') { + sourceConfig.listen_port = + parseInt(document.getElementById('srtListenPort').value, 10) || 9000; + } else { + const url = document.getElementById('srtUrl').value.trim(); + if (!url) { + alert('SRT caller mode requires a source URL'); + return; + } + sourceConfig.url = url; + } + } else if (sourceType === 'rtmp') { - sourceConfig.url = document.getElementById('rtmpUrl').value; + const mode = document.getElementById('rtmpMode').value; + sourceConfig.mode = mode; + if (mode === 'listener') { + sourceConfig.listen_port = + parseInt(document.getElementById('rtmpListenPort').value, 10) || 1935; + sourceConfig.stream_key = + document.getElementById('rtmpStreamKey').value.trim() || 'stream'; + } else { + const url = document.getElementById('rtmpUrl').value.trim(); + if (!url) { + alert('RTMP caller mode requires a source URL'); + return; + } + sourceConfig.url = url; + } } const data = {