feat: local recording pipeline, output-window hardening, persisted LF… - #11
feat: local recording pipeline, output-window hardening, persisted LF…#11dargamenteria wants to merge 1 commit into
Conversation
…O/color, tuning Local recording (electron/main.cjs), added as a fallback for an unreliable v4l2loopback path: - capturePage() -> ffmpeg -> mp4, mirroring the existing v4l2:start pipeline - h264_nvenc hardware encode instead of libx264 (software encode alone was enough to peg all CPU cores during a session) - -use_wallclock_as_timestamps + -fps_mode cfr so a session with dropped frames encodes at the correct real-time duration/speed instead of condensed/sped-up - in-flight capture guards on both record:start and v4l2:start's capture loops -- setInterval could otherwise fire again before the previous capturePage() (an async IPC round-trip) resolved, letting frames land out of chronological order and undermining the wallclock-timestamp fix - start/stop timestamps baked into the output filename as a duration fallback that survives file copies, independent of mtime - output window's aspect ratio locked (setAspectRatio) so an accidental resize can't desync it from the 16:9 assumption baked into both capture paths Other fixes: - video separator can go to 0 (instant cut, no gap) or 1; fixed a bug where 0 would leave the layer stuck blank forever - manual BPM floor lowered 40->20, for setting BPM to a divisor of the real tempo to slow LFO cycles and transitions together - LFO slots and per-deck color params now persist across restarts, same localStorage convention as the rest of +page.svelte's state - OBS/Twitch secret-store reliability, chat-poll/streaming UX, and video-loop compositing refinements Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Hello, i just saw the pr i have a bit work to do before then i take a look to it and let you know if all is good ! |
kushiemoon-dev
left a comment
There was a problem hiding this comment.
Went through this — the v4l2/record capture fixes and the LFO multi-beat thing are solid catches. Left 3 comments: one question on the password-store change, two small suggestions on timing/UI.
| // daemon running at all, so fall back to Chromium's own local-file-backed | ||
| // encryption instead of just breaking secret storage. Must be set before | ||
| // app is ready. | ||
| app.commandLine.appendSwitch('password-store', 'basic'); |
There was a problem hiding this comment.
This forces password-store=basic for every Linux user, not just ones without a keyring — Chromium already auto-detects and falls back to basic when no libsecret/kwallet backend is found, so this only matters if that auto-detection itself was failing for you. As-is it also drops GNOME/KDE users from a real keyring down to Chromium's basic obfuscation, which isn't real encryption. Might be worth gating this on XDG_CURRENT_DESKTOP/DESKTOP_SESSION instead of forcing it always-on. What was actually happening on your end — no keyring daemon at all, or auto-detect picking the wrong backend?
| // stderr output as failure made this report "failed" on every successful | ||
| // start (v4l2Error held onto a random benign banner line, e.g. the | ||
| // copyright notice, forever after). | ||
| await new Promise((r) => setTimeout(r, 150)); |
There was a problem hiding this comment.
150ms is pretty tight for ffmpeg to actually die on a slower failure (bad device perms, GPU issues) — this can return ok:true right before it dies. Not blocking, just a bit more headroom:
| await new Promise((r) => setTimeout(r, 150)); | |
| await new Promise((r) => setTimeout(r, 400)); |
| // Allow ffmpeg a short moment to fail fast (ENOENT, bad path, etc.). Gate | ||
| // on whether the process actually died, NOT whether recordError got set — | ||
| // see the identical fix/comment on v4l2:start above for why. | ||
| await new Promise((r) => setTimeout(r, 150)); |
There was a problem hiding this comment.
Same thing here.
| await new Promise((r) => setTimeout(r, 150)); | |
| await new Promise((r) => setTimeout(r, 400)); |
| {#each RATE_OPTIONS as opt} | ||
| <option value={opt.rate}>{opt.label}</option> | ||
| {/each} |
There was a problem hiding this comment.
If someone has a rate saved from the old free slider (0.25 step, up to 4×) that isn't one of the fixed options now, the select just shows blank even though slot.rate still works fine internally. Minor, but confusing in the UI:
| {#each RATE_OPTIONS as opt} | |
| <option value={opt.rate}>{opt.label}</option> | |
| {/each} | |
| {#each RATE_OPTIONS as opt} | |
| <option value={opt.rate}>{opt.label}</option> | |
| {/each} | |
| {#if !RATE_OPTIONS.some((opt) => opt.rate === slot.rate)} | |
| <option value={slot.rate}>{slot.rate}×</option> | |
| {/if} |
…O/color, tuning
Local recording (electron/main.cjs), added as a fallback for an unreliable v4l2loopback path:
Other fixes: