controller: persistent ffmpeg snapshot keeper — /snapshot latency 5s → 4ms

SnapshotKeeper class — single long-running ffmpeg subprocess на каждый
instance, читает output_rtsp_url непрерывно и dumps latest frame в файл
(/tmp/snapshot-keeper/<instance>.png) каждые 500 ms через ffmpeg fps=2
-update 1. /snapshot endpoint просто serves файл — disk read ~4 ms (было
~5 sec на cold ffmpeg start + RTSP negotiate + keyframe wait).

Auto-restart с exponential backoff при exit (RTSP source перезапустился,
network glitch). Cold ffmpeg fallback остаётся в endpoint — если keeper
ещё не успел dump первый PNG (первые ~1-2 sec после controller start).

UI: snapshot poll interval 700 → 250 ms (4 fps preview, было ~1.4 fps).
Keeper dump rate сейчас 2 fps — practical limit. При желании поднять
до 4 fps — fps=4 в snapshot_keeper.py (~5% доп CPU на ffmpeg PNG encode).

Применение: основной user-facing path = visual overlay editor http://controller:8083/.
Раньше polling 700 ms показывал тот же frame несколько раз пока ffmpeg
запускался. Сейчас preview почти real-time, drag-and-drop reference точный.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
gx
2026-05-26 22:16:45 +01:00
parent 0e9a353d75
commit d0e34c9d31
4 changed files with 135 additions and 2 deletions
@@ -225,7 +225,10 @@ function initVideo() {
busy = false;
}
tick();
setInterval(tick, 700);
// 250 ms = 4 fps preview. Keeper dumps PNG @ 2 fps в файл, /snapshot reads
// disk за ~4 ms — practical limit это keeper update rate (можно поднять
// до fps=4 в snapshot_keeper.py если нужно ещё быстрее).
setInterval(tick, 250);
// expose для syncEditorBounds
window.__previewEl = img;
window.__previewSize = () => ({w: nativeW, h: nativeH});