Skip to content

Recording feature#76

Open
TheTechromancer wants to merge 4 commits into
devfrom
recording-feature
Open

Recording feature#76
TheTechromancer wants to merge 4 commits into
devfrom
recording-feature

Conversation

@TheTechromancer

@TheTechromancer TheTechromancer commented Jul 11, 2026

Copy link
Copy Markdown
Member
2026-07-10_21-38-40.mp4

Process recording (timelapse)

Implements the roadmap's "Process recording": a passive recorder that captures the canvas as you paint, persists inside the .darkly file, stays out of RAM, compresses via inter-frame video encoding, and exports to MP4/GIF.

Architecture

  • Engine (Rust) captures, frontend encodes. A new session-state ProcessRecorder (engine/process_recording.rs) samples a new monotonic Document::revision counter — bumped at the UndoStack::push/coalesce_property chokepoints and in apply_undo — and, when the document changed, soft-downscales the composite into a fixed even-aligned RGBA target (aspect-fit letterboxed on black, so canvas resizes never change the encoder frame size) and reads it back asynchronously. Min-interval throttle (default 1.5 s) with a trailing capture so a burst's final state is always recorded; never captures mid-stroke; a full completed-queue or in-flight readback skips the capture without consuming the revision. The engine knows nothing about codecs or persistence.
  • Incremental encoding via WebCodecs VideoEncoder in a module worker (frontend/src/recording/worker.ts), which owns OPFS createSyncAccessHandle appends. Codec negotiation (codec.ts) probes H.264 → VP9, stepping resolution down on rejection, with bitrate clamp(w·h·fps·0.07, 1–12 Mbps). Encoder errors and storage failures (incl. QuotaExceededError) share one path: finalize segment → retry once on a fresh segment → disable for the session with a toast.
  • Storage model (segments.ts, pure/unit-tested): one keyframe-led segment per encoder run; chunks framed [u32le len][u8 key][u64le timestampUs][payload], crash-safe at every byte (torn tail dropped). Working recording lives in OPFS scratch keyed <sessionId>~<recoveryId> (the recovery store's attribution scheme); .darkly saves embed it under recording/ (.bin stored, not deflated), opens absorb it back into scratch, autosave snapshots exclude it (the scratch is already crash-safe). Recovery restore adopts the crashed tab's scratch; clean close / recovery-discard / boot orphan-sweep clear it — the snapshot store's exact lifecycle. The Rust loader passes recording/* zip entries through untouched.
  • Export (exportTimelapse.ts): MP4 via Mediabunny — when all segment decoder configs match, encoded packets pass straight through (EncodedVideoPacketSource) with continuous synthetic timestamps (frame N at N/fps); mismatched configs (mid-recording resolution change) fall back to decode → uniform re-encode. GIF via gifenc (~480 px, ~15 fps stride). New File → Export Timelapse… modal: MP4/GIF, playback fps, frame count / duration / on-disk size, and a Delete-recording button.

Settings

New recording config section (auto-surfaced in Settings): enabled (default on, with a one-time first-capture toast), minIntervalSeconds (0.5–10), maxLongEdge (1280/1920/2560/3840, hard-capped by encoder negotiation). All three are live — toggling stops/starts capture, resolution changes roll a new segment.

Wire protocol

Two new requests: set_recording_params (post) and poll_recording_frame (send, binary side-channel), mirroring poll_export_result; TS client regenerated.

Tests

  • Rust: revision-counter unit tests at the undo chokepoints; 6 GPU integration tests (tests/process_recording.rs) covering capture dims/content, throttle + trailing capture, undo-triggered capture, letterbox bars, mid-stroke gating, and full-queue drop semantics.
  • Vitest: chunk-framing round-trip + torn-tail tolerance at every truncation point, decoder-config compatibility, codec ladder step-down / bitrate policy / throwing-probe handling.
  • All CI gates pass: fmt, clippy (native + wasm), cargo test --workspace (980 tests, single-threaded), wasm-pack build, tsc, svelte-check, vite build, vitest (472 tests).

Dependencies

Adds mediabunny and gifenc to the frontend (export-time only).

@codecov

codecov Bot commented Jul 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.82022% with 22 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...s/darkly/src/engine/protocol/handlers/recording.rs 56.25% 14 Missing ⚠️
crates/darkly/src/engine/process_recording.rs 97.66% 5 Missing ⚠️
crates/darkly/src/engine/rendering.rs 84.21% 3 Missing ⚠️

📢 Thoughts on this report? Let us know!

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.

1 participant