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
This commit is contained in:
parent
7aa07c6708
commit
0a5b4d6191
1 changed files with 184 additions and 14 deletions
|
|
@ -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 @@
|
|||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Source Config Fields -->
|
||||
<!-- Source Config Fields — rebuilt dynamically per source type -->
|
||||
<div id="sourceConfigFields" class="conditional-fields"></div>
|
||||
|
||||
<div class="form-group">
|
||||
|
|
@ -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 = `
|
||||
<div class="recorder-thumbnail ${isRecording ? 'recording' : ''}">
|
||||
|
|
@ -682,7 +728,7 @@
|
|||
<div class="recorder-meta">
|
||||
<div class="recorder-meta-item">
|
||||
<span class="recorder-meta-label">Source</span>
|
||||
<span class="recorder-meta-value">${recorder.source_config?.url || recorder.source_config?.device || '—'}</span>
|
||||
<span class="recorder-meta-value">${sourceDisplay}</span>
|
||||
</div>
|
||||
<div class="recorder-meta-item">
|
||||
<span class="recorder-meta-label">Codec</span>
|
||||
|
|
@ -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 @@
|
|||
</div>
|
||||
`;
|
||||
fieldsContainer.classList.add('visible');
|
||||
|
||||
} else if (sourceType === 'srt') {
|
||||
fieldsContainer.innerHTML = `
|
||||
<div class="form-group">
|
||||
<label class="form-label">SRT URL</label>
|
||||
<input type="text" id="srtUrl" class="form-input" placeholder="srt://host:port">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Mode</label>
|
||||
<select id="srtMode" class="form-select">
|
||||
<option value="listener">Listener</option>
|
||||
<option value="caller">Caller</option>
|
||||
<select id="srtMode" class="form-select" onchange="updateSrtModeFields()">
|
||||
<option value="listener">Listener — encoder pushes to this recorder</option>
|
||||
<option value="caller">Caller — recorder pulls from source</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Listener fields (default) -->
|
||||
<div id="srtListenerFields" style="display:flex;flex-direction:column;gap:var(--spacing-md);">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Listen Port</label>
|
||||
<input type="number" id="srtListenPort" class="form-input"
|
||||
value="9000" min="1024" max="65535"
|
||||
oninput="refreshSrtInfo()">
|
||||
</div>
|
||||
<div class="connection-info">
|
||||
<div class="connection-info-label srt">📡 Push SRT to this address</div>
|
||||
<div class="connection-info-url" id="srtConnectionInfo">
|
||||
srt://<server-ip>:9000?mode=caller
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Caller fields (hidden initially) -->
|
||||
<div id="srtCallerFields" style="display:none;flex-direction:column;gap:var(--spacing-md);">
|
||||
<div class="form-group">
|
||||
<label class="form-label">SRT Source URL</label>
|
||||
<input type="text" id="srtUrl" class="form-input"
|
||||
placeholder="srt://host:port">
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
fieldsContainer.classList.add('visible');
|
||||
|
||||
} else if (sourceType === 'rtmp') {
|
||||
fieldsContainer.innerHTML = `
|
||||
<div class="form-group">
|
||||
<label class="form-label">RTMP URL</label>
|
||||
<input type="text" id="rtmpUrl" class="form-input" placeholder="rtmp://host:port/app/stream">
|
||||
<label class="form-label">Mode</label>
|
||||
<select id="rtmpMode" class="form-select" onchange="updateRtmpModeFields()">
|
||||
<option value="listener">Listener — encoder pushes to this recorder</option>
|
||||
<option value="caller">Caller — recorder pulls from source</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Listener fields (default) -->
|
||||
<div id="rtmpListenerFields" style="display:flex;flex-direction:column;gap:var(--spacing-md);">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Listen Port</label>
|
||||
<input type="number" id="rtmpListenPort" class="form-input"
|
||||
value="1935" min="1024" max="65535"
|
||||
oninput="refreshRtmpInfo()">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Stream Key</label>
|
||||
<input type="text" id="rtmpStreamKey" class="form-input"
|
||||
value="stream" placeholder="e.g., live"
|
||||
oninput="refreshRtmpInfo()">
|
||||
</div>
|
||||
<div class="connection-info">
|
||||
<div class="connection-info-label rtmp">📡 Push RTMP to this address</div>
|
||||
<div class="connection-info-url" id="rtmpConnectionInfo">
|
||||
rtmp://<server-ip>:1935/live/stream
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Caller fields (hidden initially) -->
|
||||
<div id="rtmpCallerFields" style="display:none;flex-direction:column;gap:var(--spacing-md);">
|
||||
<div class="form-group">
|
||||
<label class="form-label">RTMP Source URL</label>
|
||||
<input type="text" id="rtmpUrl" class="form-input"
|
||||
placeholder="rtmp://host:port/app/stream">
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
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://<server-ip>:${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://<server-ip>:${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 = {
|
||||
|
|
|
|||
Loading…
Reference in a new issue