Recording feature#76
Open
TheTechromancer wants to merge 4 commits into
Open
Conversation
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
.darklyfile, stays out of RAM, compresses via inter-frame video encoding, and exports to MP4/GIF.Architecture
ProcessRecorder(engine/process_recording.rs) samples a new monotonicDocument::revisioncounter — bumped at theUndoStack::push/coalesce_propertychokepoints and inapply_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.VideoEncoderin a module worker (frontend/src/recording/worker.ts), which owns OPFScreateSyncAccessHandleappends. Codec negotiation (codec.ts) probes H.264 → VP9, stepping resolution down on rejection, with bitrateclamp(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.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);.darklysaves embed it underrecording/(.binstored, 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 passesrecording/*zip entries through untouched.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
recordingconfig 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) andpoll_recording_frame(send, binary side-channel), mirroringpoll_export_result; TS client regenerated.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.cargo test --workspace(980 tests, single-threaded), wasm-pack build,tsc,svelte-check, vite build, vitest (472 tests).Dependencies
Adds
mediabunnyandgifencto the frontend (export-time only).