Merge: library recording thumbnails live preview

This commit is contained in:
Zac Gaetano 2026-06-02 01:36:00 +00:00
commit 4d1b23959c

View file

@ -491,13 +491,6 @@ function HlsPreview({ assetId, recorderId, muted = true, controls = false, class
/* ===== Recorders ===== */
function _normRecorder(r) {
let elapsed = '·';
if (r.status === 'recording' && r.started_at) {
const s = Math.floor((Date.now() - new Date(r.started_at)) / 1000);
elapsed = String(Math.floor(s / 3600)).padStart(2, '0') + ':' +
String(Math.floor((s % 3600) / 60)).padStart(2, '00') + ':' +
String(s % 60).padStart(2, '0');
}
const cfg = r.source_config || {};
return {
...r,
@ -505,8 +498,9 @@ function _normRecorder(r) {
url: cfg.url || cfg.address || cfg.srt_url || cfg.rtmp_url || r.source_type || '·',
codec: r.recording_codec || '·',
res: r.recording_resolution || '·',
framerate: r.recording_framerate || 'native',
node: r.node_id ? r.node_id.slice(0, 8) : 'primary',
elapsed,
elapsed: '·',
bitrate: '·',
health: 100,
audio: false,
@ -610,15 +604,43 @@ function RecorderRow({ recorder: initialRecorder, onRefresh }) {
return () => clearInterval(id);
}, [isRec, recorder.id]);
// Tick elapsed every second while recording. Seed from liveStatus.duration
// (authoritative from the capture container) when available; fall back to
// wall-clock diff from recorder.started_at so the counter never freezes.
const [elapsedSecs, setElapsedSecs] = React.useState(0);
React.useEffect(() => {
if (!isRec) { setElapsedSecs(0); return; }
const base = () => {
if (liveStatus && liveStatus.duration != null) return liveStatus.duration;
if (recorder.started_at) return Math.floor((Date.now() - new Date(recorder.started_at).getTime()) / 1000);
return 0;
};
// Snap to latest authoritative value immediately, then tick from there.
const anchor = { at: Date.now(), secs: base() };
setElapsedSecs(anchor.secs);
const id = setInterval(() => {
setElapsedSecs(anchor.secs + Math.floor((Date.now() - anchor.at) / 1000));
}, 1000);
return () => clearInterval(id);
// Re-anchor whenever liveStatus.duration arrives from the poll.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isRec, liveStatus && liveStatus.duration, recorder.started_at]);
const displayElapsed = React.useMemo(() => {
if (liveStatus && liveStatus.duration != null) {
const d = Math.max(0, liveStatus.duration);
return String(Math.floor(d / 3600)).padStart(2, '0') + ':' +
String(Math.floor((d % 3600) / 60)).padStart(2, '0') + ':' +
String(d % 60).padStart(2, '0');
if (!isRec) return '·';
const d = Math.max(0, elapsedSecs);
return String(Math.floor(d / 3600)).padStart(2, '0') + ':' +
String(Math.floor((d % 3600) / 60)).padStart(2, '0') + ':' +
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.elapsed;
}, [liveStatus, recorder.elapsed]);
return recorder.framerate || 'native';
}, [isRec, liveStatus, recorder.framerate]);
const displaySignal = liveStatus
? (liveStatus.signal || '·')
@ -709,12 +731,10 @@ function RecorderRow({ recorder: initialRecorder, onRefresh }) {
{displaySignal}
</div>
</div>
{liveStatus?.currentFps != null && (
<div className="recorder-stat">
<div className="stat-label">FPS</div>
<div className="stat-val mono">{Number(liveStatus.currentFps).toFixed(1)}</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 && (