Skip to content

feat: local recording pipeline, output-window hardening, persisted LF… - #11

Open
dargamenteria wants to merge 1 commit into
kushiemoon-dev:mainfrom
dargamenteria:main
Open

feat: local recording pipeline, output-window hardening, persisted LF…#11
dargamenteria wants to merge 1 commit into
kushiemoon-dev:mainfrom
dargamenteria:main

Conversation

@dargamenteria

Copy link
Copy Markdown

…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

…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>
@kushiemoon-dev

Copy link
Copy Markdown
Owner

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 kushiemoon-dev left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread electron/main.cjs
// 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');

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Comment thread electron/main.cjs
// 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));

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

Suggested change
await new Promise((r) => setTimeout(r, 150));
await new Promise((r) => setTimeout(r, 400));

Comment thread electron/main.cjs
// 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));

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same thing here.

Suggested change
await new Promise((r) => setTimeout(r, 150));
await new Promise((r) => setTimeout(r, 400));

Comment on lines +59 to +61
{#each RATE_OPTIONS as opt}
<option value={opt.rate}>{opt.label}</option>
{/each}

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

Suggested change
{#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}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants