On macOS versions before Sonoma (14.0), SCStreamConfiguration does not
support the captureMicrophone/microphoneCaptureDeviceID selectors. The
native helper was throwing a fatal error, blocking recording entirely
when microphone was enabled.
Now the Swift helper logs MICROPHONE_CAPTURE_UNAVAILABLE and continues
capture without microphone. The Node handler detects this signal and
returns microphoneFallbackRequired to the renderer, which starts a
browser-side MediaRecorder capturing the mic via getUserMedia. On stop,
the audio blob is saved as a .mic.webm sidecar alongside the native
recording, where the existing companion audio detection picks it up.
Fixes: 'Native microphone capture is unavailable on this macOS/Xcode
runtime' error on older macOS versions.
- Integrate extension render hooks into frameRenderer and modernFrameRenderer
- Add modernFrameRenderer for compositing extensions during export
- Wire extension audio processing in audioEncoder
- Support extension hooks in gif and video export paths
- Extension loader with manifest validation, ID sanitization, and path traversal protection
- Permission-gated runtime API (render, cursor, audio, timeline, ui, assets, export)
- Extension host with lifecycle management (activate/deactivate)
- Marketplace client for browsing, downloading, and installing extensions
- Extension Manager UI panel with enable/disable/uninstall
- IPC bridge for extension discovery, management, and marketplace operations
- Proxy guard on window.electronAPI to block extension access to privileged IPC
- Zip-slip protection via post-extraction directory traversal validation
- Safe PowerShell invocation for Windows zip extraction (no string interpolation)
- File URL resolution with path escape prevention
- Remove dead time= progress matching (no decode pass = no progress output)
- Remove stale comments about ffmpeg success/fallback behavior
- Lower timeout from 30s to 5s (header read is near-instant)
- Drop maxBuffer override (minimal stderr output now)
- Add -hide_banner to reduce stderr noise
- Remove -f null - from probeMediaDurationSeconds() ffmpeg args
so the command always exits non-zero (no output file specified)
- Without -f null -, ffmpeg exits code 1 and stderr lands in the
catch block where Duration is parsed, fixing the bug where
duration returned 0 on valid recordings
- probeMediaDurationSeconds returning 0 caused the
videoDuration > 0 gate to skip all audio sync correction,
leaving system audio misaligned in the final recording
- Add rowId to allRegionSpans so collision detection only considers
siblings on the same timeline row
- Split clampToNeighbours into clampResizedSpanToNeighbours and
clampDraggedSpanToNeighbours for resize vs drag semantics
- Preserve original clip duration on drag (pure translation) to prevent
pixel-to-ms rounding drift at different zoom levels
The analytical velocity fix makes the spring settle more precisely,
slightly increasing frame count to convergence. Relax threshold
from 120 to 400 frames.
Use analytical velocity (closed-form derivative) instead of frame-to-frame
finite differences for spring state propagation. Add overshoot clamp for
overdamped springs (ζ ≥ 1) to prevent counter-oscillation when the zoom
easing target reverses direction mid-animation.
Align decoded frame timestamps with the media timeline so trimmed exports do not end early on recordings whose video stream starts after the container timeline.
When deleting the Whisper model failed, the frontend kept stale state
(whisperModelDownloadStatus stayed as 'downloaded'), which blocked
re-downloading because the download handler saw the model as already
present. On the backend, no progress event was sent on failure, leaving
the renderer out of sync.
Frontend: reset whisperModelDownloadStatus and progress to idle on
delete failure so re-download is unblocked.
Backend: verify whether the file was actually removed despite the error
and send appropriate progress events in either case.
Closes#152
The hasOverlap function in TimelineEditor checked zoom, trim, speed, and
audio regions for overlap but was missing clip regions entirely. This
caused clips to freely overlap each other when dragged or resized,
leading to playback bugs when trimmed clips intersected.
Add clip region overlap detection so the existing clampToNeighbours
logic in TimelineWrapper correctly constrains clip boundaries.
Closes#166
- Add getHudOverlayDisplay() helper using getDisplayMatching()
to detect which display the HUD is currently on
- Replace getPrimaryDisplay() in getHudOverlayBounds(),
set-hud-overlay-compact-width, set-hud-overlay-measured-height
handlers, and createHudOverlayWindow() maxHeight constraint
- Falls back to primary display when HUD window doesn't exist
Tested on Windows 11 with dual-monitor setup. HUD stays on
the external monitor through recording start/stop and
expand/collapse cycles.
When loading a saved project on startup, applyLoadedProject() overwrites
all editor state — including user preference settings like padding,
aspect ratio, and export format — with potentially stale project values.
The subsequent save effect then writes those stale values back to
localStorage, effectively erasing the user's latest preferences.
Now re-applies preference-level settings from localStorage after project
load so that per-user settings (padding, border radius, aspect ratio,
export format/quality/encoding, gif settings) survive across sessions.
Fixes#197
When recording a window on a secondary monitor with a different DPI,
getNormalizedCursorPoint() used the primary display's scale factor for
both cursor and window-bounds conversion. This produced incorrect
normalised coordinates. Now resolves the display that contains the
target window and uses that display's scale factor instead.
Fixes#204
The Swift helper binaries were compiled with -target macos14.0, causing
dyld symbol-not-found crashes on macOS 13. The ScreenCaptureKit audio
APIs require macOS 13.0 minimum, so the deployment target is lowered to
13.0 and an #available guard is added for the macOS 14-only
ignoreShadowsSingleWindow API. README updated accordingly.
Fixes#199, fixes#200
Instead of freezing at 'Finalizing 99%' while audio renders in real-time,
the export UI now shows 'Rendering audio XX%' with a live progress bar and
an explanatory note about why real-time playback is required.
Progress is reported every 250ms from the RAF tick loop in AudioProcessor
and piped through VideoExporter to the UI via the existing onProgress callback.
The zoom spring animation in both frameRenderer and modernFrameRenderer
was using performance.now() (wall-clock time) to compute spring deltas.
During export, wall-clock time and content time are decoupled — a frame
representing 33ms of video may render in 3ms or 500ms. This caused zoom
animations to appear choppy/laggy in exports while previews were smooth.
Switched to content-time deltas (timeMs) so the spring advances at the
same rate as the video regardless of actual render speed.
Also bumps version to 1.1.20.
Use F_OK instead of X_OK for Windows fs.access check (X_OK is meaningless
on Windows). Wrap spawn() in try/catch to prevent uncaught exception if the
binary is missing or blocked by antivirus.