fix(capture): add 5s pre-roll delay to stabilize SDI, remove framerate display

- services/capture/src/capture-manager.js:
  - Added 5s pre-roll delay for Deltacast, Blackmagic, and SDI capture paths.
  - Activates when  is present.
  - Spawns the  process immediately, drains/discards the unstable frames for 5 seconds, and then pipes the stdout of the  to the actual  process.
  - Keeps the master output perfectly clean from frame 0.

- services/web-ui/public/screens-ingest.jsx:
  - Removed currentFps framerate display from both recording and idle status states.
This commit is contained in:
Wild Dragon Dev 2026-06-03 17:08:09 +00:00
parent 9dc86aa3b6
commit b6545e61a9
2 changed files with 15 additions and 19 deletions

View file

@ -7,6 +7,8 @@ import { createUploadStream } from './s3/client.js';
const S3_BUCKET = process.env.S3_BUCKET || 'wild-dragon';
const PRE_ROLL_SECONDS = parseInt(process.env.PRE_ROLL_SECONDS || '5', 10);
// Growing-files mode: writes the master to a local SMB-backed share that the
// editor can mount, instead of streaming to S3 in real time. The promotion
@ -1051,13 +1053,24 @@ exit "$BMXRC"
// The stop handler sets needsProxy=true so the worker picks it up.
const proxyKey = null;
const startedAt = new Date().toISOString();
this._sessionIdForBridge = sessionId;
const { inputArgs, isNetwork, bridgeProcess = null, audioFifo = null, interlaced = false, audioInputIndex = 0 } = await this._buildInputArgs({
sourceType, sourceBackend, device, port, board, sourceUrl, listen, listenPort, streamKey,
});
// ── Pre-roll: discard initial unstable frames ────────────────────────────
if (bridgeProcess && (sourceType === 'deltacast' || sourceType === 'blackmagic' || sourceType === 'sdi')) {
console.log(`[capture] pre-rolling: discarding ${PRE_ROLL_SECONDS}s of frames`);
// Attach temporary drain listener.
bridgeProcess.stdout.on('data', () => {});
await new Promise(r => setTimeout(r, PRE_ROLL_SECONDS * 1000));
bridgeProcess.stdout.removeAllListeners('data');
console.log(`[capture] pre-roll complete.`);
}
const startedAt = new Date().toISOString();
const recordingStartedAt = Date.now();
// Audio input index is returned EXPLICITLY by _buildInputArgs (audioInputIndex)
// rather than guessed from sourceType/FC_SLOT_ID — that guess was wrong for
// the legacy deltacast FIFO path (which has audio at input 1 but no FC_SLOT_ID),

View file

@ -759,14 +759,6 @@ function RecorderRow({ recorder: initialRecorder, onRefresh }) {
String(d % 60).padStart(2, '0');
}, [isRec, elapsedSecs]);
// Show live fps when recording and signal is healthy; fall back to configured value.
const displayFramerate = React.useMemo(() => {
if (isRec && liveStatus && liveStatus.currentFps != null && liveStatus.currentFps > 0) {
return Number(liveStatus.currentFps).toFixed(2) + ' fps';
}
return recorder.framerate || 'native';
}, [isRec, liveStatus, recorder.framerate]);
const displaySignal = liveStatus
? (liveStatus.signal || '·')
: (isRec ? 'connecting…' : '·');
@ -861,10 +853,6 @@ function RecorderRow({ recorder: initialRecorder, onRefresh }) {
{displaySignal}
</div>
</div>
<div className="recorder-stat">
<div className="stat-label">Framerate</div>
<div className="stat-val mono">{displayFramerate}</div>
</div>
</div>
<div className="recorder-actions">
{!isRec && (
@ -958,11 +946,6 @@ function CapturePortChip({ port, sigEntry }) {
<span style={{ fontSize: 9.5, fontWeight: 700, letterSpacing: '0.05em', color }}>
{label}
</span>
{sigEntry && sigEntry.currentFps != null && (
<span style={{ fontSize: 9.5, color: 'var(--text-4)', fontFamily: 'var(--font-mono)' }}>
{Number(sigEntry.currentFps).toFixed(1)} fps
</span>
)}
</div>
</div>
</div>