Skip to content

Conversation

@richiemcilroy
Copy link
Member

@richiemcilroy richiemcilroy commented Nov 17, 2025

Summary by CodeRabbit

  • New Features

    • Automatic microphone and camera input restoration when recording begins if inputs were previously disconnected
    • Camera preview window now displays in Instant recording mode
  • Improvements

    • Enhanced recording controls interface with updated interactive area tracking and layout organization
    • Improved camera frame streaming stability with automatic reconnection handling
    • Refined camera preview window close behavior

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 17, 2025

Walkthrough

The changes refactor input management by extracting common logic into centralized helper functions, implement automatic input restoration from stored settings during recording startup and window initialization, add robust camera frame forwarding with reattachment capability, enforce monotonic video timestamps, and restructure the recording UI to support interactive area bounds tracking for controls.

Changes

Cohort / File(s) Summary
Input Management Refactoring
apps/desktop/src-tauri/src/lib.rs, apps/desktop/src-tauri/src/deeplink_actions.rs
Extracted input application logic into new apply_mic_input and apply_camera_input helper functions; refactored set_mic_input and set_camera_input to delegate to these new functions; updated call sites to use new function names.
Input Restoration on Startup
apps/desktop/src-tauri/src/recording.rs, apps/desktop/src-tauri/src/windows.rs
Added restore_inputs_from_store_if_missing in recording.rs and restore_recording_inputs_if_idle in windows.rs to automatically reload and restore saved mic/camera inputs when missing; integrated restoration calls into recording startup and window initialization paths.
Recording UI Restructuring
apps/desktop/src/routes/in-progress-recording.tsx, apps/desktop/src/routes/camera.tsx
Added interactive area bounds tracking and fake window management for recording controls in in-progress-recording.tsx; replaced camera state mutation with direct window close in camera.tsx.
Output Pipeline & Video Handling
crates/recording/src/output_pipeline/core.rs, crates/recording/src/output_pipeline/ffmpeg.rs, crates/recording/src/sources/camera.rs
Refactored video encoder task to use cancellation-aware select loop; added frame duration tracking and automatic timestamp adjustment to enforce monotonic timestamps in Mp4Muxer; implemented robust camera frame forwarding loop with reattachment logic on channel disconnection.
Camera Feed Example
crates/recording/examples/camera_stream.rs
New example demonstrating camera feed initialization, frame acquisition via lock, and frame counting over a 5-second window.

Sequence Diagrams

sequenceDiagram
    participant App
    participant RecordingStartup as Recording Start
    participant StateStore as Settings Store
    participant InputApply as apply_mic/camera_input
    participant AppState

    RecordingStartup->>RecordingStartup: Check if recording active
    alt No recording & inputs missing
        RecordingStartup->>StateStore: Load RecordingSettingsStore
        StateStore-->>RecordingStartup: Settings loaded
        RecordingStartup->>InputApply: apply_mic_input(state, label)
        InputApply->>AppState: Update mic state
        InputApply-->>RecordingStartup: Result
        RecordingStartup->>InputApply: apply_camera_input(app, state, id)
        InputApply->>AppState: Update camera state & show preview
        InputApply-->>RecordingStartup: Result
        Note over RecordingStartup: Log warnings on failure, continue
    else Recording active or inputs present
        RecordingStartup->>RecordingStartup: Skip restoration
    end
    RecordingStartup->>RecordingStartup: Proceed with recording
Loading
sequenceDiagram
    participant CameraTask as Camera Forwarding Task
    participant FrameReceiver as Frame Receiver
    participant VideoEncoder as Video Encoder
    participant FeedLock as Feed Lock

    loop Continuous Frame Loop
        CameraTask->>FrameReceiver: Await frame
        alt Frame received
            FrameReceiver-->>CameraTask: Frame data
            CameraTask->>VideoEncoder: Send frame
            VideoEncoder-->>CameraTask: Ack
        else Receiver error (channel closed)
            FrameReceiver-->>CameraTask: Error
            CameraTask->>CameraTask: Create new bounded channel
            CameraTask->>FeedLock: Send AddSender (reattach)
            FeedLock-->>CameraTask: Result (log warning if fails)
            CameraTask->>CameraTask: Switch to new receiver
            Note over CameraTask: Continue forwarding with new channel
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Video timestamp handling (ffmpeg.rs): Frame duration tracking and automatic timestamp adjustment logic requires careful verification that monotonic constraints are met and timestamps don't unexpectedly jump.
  • Camera reattachment mechanism (camera.rs): Robust forwarding loop with channel reattachment on disconnection introduces new error paths and control flow; ensure reattachment logic handles race conditions correctly.
  • Input restoration flow (recording.rs, windows.rs): New restoration paths during startup and idle states need verification that they don't conflict with active recordings or duplicate input settings.
  • UI bounds tracking (in-progress-recording.tsx): Complex state management for fake window bounds and interactive area refs; verify cleanup paths and boundary updates work correctly across component lifecycle.
  • Encoder cancellation refactor (core.rs): Conversion from run_until_cancelled to explicit tokio::select! loop requires careful review of cancellation semantics and frame-handling edge cases.

Possibly related PRs

Suggested labels

codex

Poem

🐰 Inputs restored, timestamps aligned,
Frames reattach when channels unwind,
Interactive bounds now guard the controls,
A hop through pipelines toward recording goals! 🎬✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive Title is vague and uses non-descriptive phrases ('numerous', 'etc') that obscure the substantive changes; doesn't convey what the main changes are. Revise title to be more specific about the primary changes, such as 'refactor: Extract input application logic into reusable helper functions' or include the most impactful change instead of using placeholder language.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch camera-fixes

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (7)
crates/recording/src/output_pipeline/ffmpeg.rs (1)

106-126: Consider logging timestamp adjustments for debugging.

The timestamp adjustment logic correctly enforces non-decreasing timestamps by advancing out-of-order frames to last_ts + frame_duration. However, these corrections happen silently, which could make it harder to diagnose issues with the video source.

Consider adding a debug-level log when timestamps are adjusted:

                 if let Some(last_ts) = self.last_video_ts {
                     if timestamp <= last_ts {
+                        tracing::debug!(
+                            "Adjusted video timestamp from {:?} to {:?}",
+                            timestamp,
+                            last_ts + frame_duration
+                        );
                         timestamp = last_ts + frame_duration;
                     }
                 }
crates/recording/src/sources/camera.rs (1)

33-65: Forwarding loop is robust; consider using SetupCtx::spawn for supervision

The reattachment logic around flume receiver errors and the error/warn logging look solid and should make the camera pipeline more resilient.

One thing to consider is wiring this task through SetupCtx::spawn instead of a bare tokio::spawn, so it participates in the output pipeline’s lifecycle/logging (like the other tasks spawned from SetupCtx). That would avoid an extra detached task and give you consistent tracing of task start/finish.

apps/desktop/src-tauri/src/deeplink_actions.rs (1)

9-12: Confirm semantics when deeplinks explicitly pass None for camera/mic

Routing the deeplink path through apply_camera_input / apply_mic_input is good for consistency.

One subtle edge case: start_recording now calls restore_inputs_from_store_if_missing, which restores mic/camera from RecordingSettingsStore whenever selected_* is None. If a deeplink explicitly passes camera: null or mic_label: null, apply_*_input will set the selection to None, and the subsequent restore step may bring back stored inputs, effectively ignoring the deeplink’s “no camera/mic” intent.

If “None means restore last-used” is not what you want for deeplinks, you may want to either:

  • Skip restore_inputs_from_store_if_missing for deeplink-started recordings, or
  • Pass a flag into start_recording / the restore helper to indicate that inputs were explicitly chosen for this invocation and should not be auto-restored.

Also applies to: 122-124

apps/desktop/src-tauri/src/windows.rs (1)

23-30: Idle restoration helper is sound; double‑check when it should run

The new restore_recording_inputs_if_idle helper looks well-contained:

  • It tolerates missing/invalid RecordingSettingsStore entries and just logs a warn!.
  • It skips work when both mic_name and camera_id are None.
  • It checks is_recording_active_or_pending() before touching inputs, and applies mic/camera via the centralized apply_*_input helpers with warnings on failure.

Two small points to confirm:

  1. When to run: ShowCapWindow::Main calls restore_recording_inputs_if_idle(app) unconditionally (regardless of enable_new_recording_flow). If the intent was to only auto-restore inputs for the new recording flow, you may want to gate this call on new_recording_flow.

  2. Possible duplicate restoration: There is now both this idle-time restoration and the restore_inputs_from_store_if_missing call at the top of start_recording. In practice it’s harmless (the helpers are idempotent), but if you only want one of these behaviors, it might be cleaner to pick a single restoration point.

Also applies to: 281-286, 802-842

apps/desktop/src-tauri/src/recording.rs (1)

50-60: Input restoration helper is reasonable; clarify “missing vs explicitly disabled” semantics

The restoration flow is generally solid:

  • It first checks recording_state and bails if a recording is active.
  • It only does work when selected_mic_label or selected_camera_id is None.
  • It handles RecordingSettingsStore::get failures with a warn! and early return.
  • Mic/camera restoration runs through the centralized apply_*_input helpers, so state and preview windows stay in sync.

One behavior to be aware of: treating selected_mic_label.is_none() / selected_camera_id.is_none() as “needs restore” means that any caller who deliberately sets these to None right before start_recording (e.g. a deeplink that intends “no camera”) can have that choice silently overridden by stored settings.

If you need to distinguish “unset/missing on startup” from “explicitly disabled for this recording”, it might be worth:

  • Threading an explicit “don’t restore inputs for this call” flag into restore_inputs_from_store_if_missing, or
  • Having the caller pass in which inputs it wants restored vs left alone, rather than inferring purely from selected_* being None.

Otherwise, the implementation looks correct and should behave well in the general “restore last-known inputs on startup” case.

Also applies to: 354-389, 399-400

apps/desktop/src/routes/in-progress-recording.tsx (1)

200-222: Effect and cleanup correctly manage fake-window lifecycle; consider optional throttling

The effect defensively removes the fake window when the ref disappears, skips zero-sized bounds, and reapplies bounds when the interactive area moves/resizes, with onCleanup ensuring final teardown. This is functionally sound.

If you ever see backend chatter from frequent setFakeWindowBounds calls (e.g., during window drags or resizes), consider tracking the last {left, top, width, height} in a local signal/memo and early-return when unchanged to avoid redundant TAURI invocations. No change needed unless profiling shows this as hot.

apps/desktop/src-tauri/src/lib.rs (1)

2561-2563: Don’t silently ignore apply_ failures before starting a recording*

In the RequestStartRecording handler, failures from apply_mic_input / apply_camera_input are currently discarded, so a recording can start with misconfigured inputs without any log signal. This isn’t fatal but makes diagnosing device issues harder.

Consider logging these errors before proceeding to start_recording:

-            let settings = RecordingSettingsStore::get(&app)
+            let settings = RecordingSettingsStore::get(&app)
                 .ok()
                 .flatten()
                 .unwrap_or_default();
 
-                let _ = apply_mic_input(app.state(), settings.mic_name).await;
-                let _ = apply_camera_input(app.clone(), app.state(), settings.camera_id).await;
+                if let Err(err) = apply_mic_input(app.state(), settings.mic_name).await {
+                    warn!("Failed to apply mic input from settings: {err}");
+                }
+                if let Err(err) =
+                    apply_camera_input(app.clone(), app.state(), settings.camera_id).await
+                {
+                    warn!("Failed to apply camera input from settings: {err}");
+                }
 
                 let _ = start_recording(app.clone(), app.state(), {

This keeps behavior the same (recording still starts) but surfaces configuration problems via tracing.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8a922a4 and 383d2a6.

📒 Files selected for processing (9)
  • apps/desktop/src-tauri/src/deeplink_actions.rs (2 hunks)
  • apps/desktop/src-tauri/src/lib.rs (3 hunks)
  • apps/desktop/src-tauri/src/recording.rs (4 hunks)
  • apps/desktop/src-tauri/src/windows.rs (3 hunks)
  • apps/desktop/src/routes/in-progress-recording.tsx (6 hunks)
  • crates/recording/examples/camera_stream.rs (1 hunks)
  • crates/recording/src/output_pipeline/core.rs (2 hunks)
  • crates/recording/src/output_pipeline/ffmpeg.rs (4 hunks)
  • crates/recording/src/sources/camera.rs (2 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Format Rust code using rustfmt and ensure all Rust code passes workspace-level clippy lints.
Rust modules should be named with snake_case, and crate directories should be in kebab-case.

Files:

  • crates/recording/src/sources/camera.rs
  • crates/recording/src/output_pipeline/ffmpeg.rs
  • crates/recording/src/output_pipeline/core.rs
  • apps/desktop/src-tauri/src/lib.rs
  • apps/desktop/src-tauri/src/deeplink_actions.rs
  • apps/desktop/src-tauri/src/recording.rs
  • apps/desktop/src-tauri/src/windows.rs
  • crates/recording/examples/camera_stream.rs
crates/*/src/**/*

📄 CodeRabbit inference engine (AGENTS.md)

Rust crates should place tests within the src/ and/or a sibling tests/ directory for each crate inside crates/*.

Files:

  • crates/recording/src/sources/camera.rs
  • crates/recording/src/output_pipeline/ffmpeg.rs
  • crates/recording/src/output_pipeline/core.rs
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use a 2-space indent for TypeScript code.
Use Biome for formatting and linting TypeScript/JavaScript files by running pnpm format.

Files:

  • apps/desktop/src/routes/in-progress-recording.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Use kebab-case for filenames for TypeScript/JavaScript modules (e.g., user-menu.tsx).
Use PascalCase for React/Solid components.

Files:

  • apps/desktop/src/routes/in-progress-recording.tsx
🧠 Learnings (2)
📚 Learning: 2025-10-28T08:39:42.230Z
Learnt from: Brendonovich
Repo: CapSoftware/Cap PR: 1305
File: crates/recording/src/output_pipeline/macos.rs:80-90
Timestamp: 2025-10-28T08:39:42.230Z
Learning: In `crates/recording/src/output_pipeline/macos.rs`, the `AVFoundationMp4Muxer` intentionally holds the `Mutex<MP4Encoder>` lock during retry attempts in `send_video_frame()` and `send_audio_frame()`. This blocking behavior is correct because frame processing must happen sequentially to prevent audio and video frames from being interleaved incorrectly in the encoder.

Applied to files:

  • crates/recording/src/sources/camera.rs
  • crates/recording/src/output_pipeline/ffmpeg.rs
  • crates/recording/src/output_pipeline/core.rs
  • apps/desktop/src-tauri/src/deeplink_actions.rs
  • crates/recording/examples/camera_stream.rs
📚 Learning: 2025-10-17T05:58:22.586Z
Learnt from: Brendonovich
Repo: CapSoftware/Cap PR: 1219
File: crates/enc-avfoundation/src/mp4.rs:350-373
Timestamp: 2025-10-17T05:58:22.586Z
Learning: In crates/enc-avfoundation/src/mp4.rs, the `finish()` method intentionally skips video extension when `is_paused` is true. This is correct behavior because if recording is paused, the video should not be extended beyond the pause point—the pause is user-initiated, unlike the case where ScreenCaptureKit stops providing frames during static content.

Applied to files:

  • crates/recording/src/output_pipeline/ffmpeg.rs
  • crates/recording/src/output_pipeline/core.rs
🧬 Code graph analysis (7)
crates/recording/src/sources/camera.rs (2)
crates/recording/src/output_pipeline/core.rs (1)
  • spawn (122-142)
apps/desktop/src-tauri/src/camera_legacy.rs (2)
  • flume (8-8)
  • flume (9-9)
crates/recording/src/output_pipeline/ffmpeg.rs (3)
crates/enc-avfoundation/src/mp4.rs (1)
  • video_frame_duration (398-411)
crates/recording/src/sources/screen_capture/mod.rs (2)
  • config (348-350)
  • info (352-354)
crates/enc-ffmpeg/src/video/h264.rs (1)
  • builder (234-236)
crates/recording/src/output_pipeline/core.rs (7)
crates/recording/src/output_pipeline/ffmpeg.rs (2)
  • timestamp (22-24)
  • send_video_frame (106-126)
crates/recording/src/sources/screen_capture/windows.rs (2)
  • timestamp (63-65)
  • std (157-157)
crates/recording/src/sources/screen_capture/macos.rs (1)
  • timestamp (61-63)
crates/recording/src/output_pipeline/macos.rs (1)
  • send_video_frame (70-103)
crates/recording/src/output_pipeline/win.rs (1)
  • send_video_frame (342-352)
crates/timestamp/src/macos.rs (1)
  • duration_since (22-31)
crates/timestamp/src/win.rs (1)
  • duration_since (30-47)
apps/desktop/src-tauri/src/deeplink_actions.rs (1)
apps/desktop/src-tauri/src/lib.rs (8)
  • apply_camera_input (428-505)
  • apply_mic_input (337-408)
  • app (1537-1538)
  • app (2597-2597)
  • app (2628-2628)
  • app (2692-2692)
  • app (2698-2698)
  • app (2922-2923)
apps/desktop/src-tauri/src/recording.rs (2)
apps/desktop/src-tauri/src/lib.rs (9)
  • apply_camera_input (428-505)
  • apply_mic_input (337-408)
  • app (1537-1538)
  • app (2597-2597)
  • app (2628-2628)
  • app (2692-2692)
  • app (2698-2698)
  • app (2922-2923)
  • None (3061-3061)
apps/desktop/src-tauri/src/recording_settings.rs (1)
  • get (29-40)
apps/desktop/src-tauri/src/windows.rs (2)
apps/desktop/src-tauri/src/lib.rs (12)
  • apply_camera_input (428-505)
  • apply_mic_input (337-408)
  • app (1537-1538)
  • app (2597-2597)
  • app (2628-2628)
  • app (2692-2692)
  • app (2698-2698)
  • app (2922-2923)
  • None (3061-3061)
  • app_handle (509-509)
  • app_handle (550-550)
  • app_handle (594-594)
apps/desktop/src-tauri/src/recording_settings.rs (1)
  • get (29-40)
crates/recording/examples/camera_stream.rs (1)
crates/recording/src/feeds/camera.rs (2)
  • camera_info (157-159)
  • from_info (189-193)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Clippy (aarch64-apple-darwin, macos-latest)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (10)
crates/recording/src/output_pipeline/core.rs (2)

432-432: LGTM: Parameter mutability removed.

The receiver is now converted to a fused stream within the task (line 461), so mutability at the parameter level is no longer needed.


453-500: Well-structured cancellation-aware video muxing.

The refactored task now handles video stream closure gracefully:

  • Uses tokio::select! to handle both cancellation signals and incoming frames
  • If the video stream ends before cancellation (line 483-490), it logs a warning and cancels the entire pipeline via stop_token_on_close
  • Ensures proper cleanup by calling muxer.lock().await.stop() after the loop

This is a significant behavioral improvement that prevents incomplete recordings when the video source stops unexpectedly.

crates/recording/src/output_pipeline/ffmpeg.rs (3)

31-32: LGTM: Timestamp tracking fields added.

These fields support the non-decreasing timestamp enforcement logic in send_video_frame. The Option types correctly handle scenarios where video encoding is disabled.


51-60: LGTM: Frame duration computed during setup.

Computing the frame duration once during encoder initialization is efficient. The tuple return pattern cleanly handles both the encoder and duration together.


129-138: Verify handling of invalid frame rates.

The frame_duration calculation uses .max(1) guards to prevent division by zero. However, consider whether additional validation is needed for edge cases:

Run this script to check if VideoInfo.frame_rate can have zero or negative values:

If zero or negative frame rates are possible, consider adding an early return with a sensible default (as done in the AVFoundation implementation at crates/enc-avfoundation/src/mp4.rs:397-410):

fn frame_duration(info: &VideoInfo) -> Duration {
    let num = info.frame_rate.numerator();
    let den = info.frame_rate.denominator();
    
    if num <= 0 || den <= 0 {
        return Duration::from_millis(33); // ~30 fps fallback
    }
    
    let nanos = ((den as u128 * 1_000_000_000u128) / num as u128).max(1);
    Duration::from_nanos(nanos as u64)
}
crates/recording/examples/camera_stream.rs (1)

1-56: Camera example looks correct and idiomatic

The example wires up CameraFeed, applies SetInput, acquires a lock, and uses a bounded channel to count frames over 5 seconds with sensible error handling. Nothing blocking here from a correctness perspective.

apps/desktop/src/routes/in-progress-recording.tsx (2)

55-89: Interactive area ref and bounds wiring look solid

Using a dedicated FAKE_WINDOW_BOUNDS_NAME plus interactiveAreaRef and derived bounds is clear and keeps the interactive area concerns nicely scoped to this component. No functional issues stand out here.

Please just confirm visually that the fake-window overlay aligns with this container across DPI scaling / multi-display setups.


510-672: New interactive area layout and drag-region placement are coherent

Wrapping the issue panel, controls, and drag handle in the interactive area (with the handle as the primary data-tauri-drag-region) matches the fake-window bounds logic and keeps the draggable affordance explicit. The mic/issue/settings controls and ARIA attributes still look consistent with prior behavior.

Given the drag-region is now mainly the right-hand handle (and the mic-off icon when no mic), double-check on Windows/macOS that window dragging still feels discoverable and that the fake-window hitbox matches the visible control bar.

apps/desktop/src-tauri/src/lib.rs (2)

334-408: Mic input helper centralization looks correct and avoids holding locks across awaits

apply_mic_input cleanly factors out the mic-handling logic: it snapshots mic_feed and optional studio handle under a read lock, avoids unnecessary work when the requested label matches the current one, detaches the studio mic feed while reconfiguring the actor, then updates App state and emits InputRestored only when the disconnected flag was actually set. This matches the existing disconnect/restoration model without obvious regressions.

Given this now runs both from the Tauri command and from startup flows, please ensure you have a test/manual path that covers:

  • Switching from one mic to another mid-recording (including pause/resume), and
  • Selecting “No Microphone” after an InputLost event,
    to confirm disconnected_inputs and the frontend issue-state behave as expected.

420-505: Camera input helper mirrors mic logic and integrates preview window behavior correctly

apply_camera_input follows the same pattern as the mic helper: it snapshots camera_feed and optional studio handle, no-ops when the same camera is already in-use, detaches from the studio handle while updating, removes or sets the camera feed accordingly, shows the camera preview when a camera is selected, then updates selected_camera_id, camera_in_use, and conditionally emits InputRestored. This keeps the recording state and device watcher invariants intact.

Please sanity-check:

  • Switching camera devices during a studio recording (including recovering from InputLost), and
  • Turning the camera off (None) while the preview window is open,
    to ensure the preview window and camera_in_use flag stay in sync with the new helper logic.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
apps/desktop/src/routes/in-progress-recording.tsx (1)

212-230: Improve bounds validation before setting fake window.

The effect uses nullish coalescing with 0 as fallback for bounds that might be undefined, but only checks for zero width/height. If left or top are undefined, they'll become 0, which might not represent the actual position during initial render.

Consider validating that all bounds properties are defined:

 const left = interactiveBounds.left ?? 0;
 const top = interactiveBounds.top ?? 0;
 const width = interactiveBounds.width ?? 0;
 const height = interactiveBounds.height ?? 0;

-if (width === 0 || height === 0) return;
+if (width === 0 || height === 0 || interactiveBounds.left == null || interactiveBounds.top == null) return;

 void commands.setFakeWindowBounds(FAKE_WINDOW_BOUNDS_NAME, {

This ensures fake window bounds are only set when the element is fully positioned.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f86e060 and b4f25eb.

📒 Files selected for processing (1)
  • apps/desktop/src/routes/in-progress-recording.tsx (6 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use a 2-space indent for TypeScript code.
Use Biome for formatting and linting TypeScript/JavaScript files by running pnpm format.

Files:

  • apps/desktop/src/routes/in-progress-recording.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Use kebab-case for filenames for TypeScript/JavaScript modules (e.g., user-menu.tsx).
Use PascalCase for React/Solid components.

Files:

  • apps/desktop/src/routes/in-progress-recording.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (5)
apps/desktop/src/routes/in-progress-recording.tsx (5)

1-1: LGTM! Clear setup for interactive bounds tracking.

The imports and constant for managing the interactive area bounds are well-named and appropriate for the new functionality.

Also applies to: 21-21, 57-57


86-88: LGTM! Proper reactive bounds tracking.

The signal and bounds tracking setup correctly follows Solid.js patterns for observing DOM element dimensions.


232-234: LGTM! Proper cleanup on unmount.

The cleanup correctly removes the fake window when the component is destroyed, preventing resource leaks.


522-686: LGTM! Well-structured UI improvements.

The UI restructure successfully:

  • Wraps controls in a tracked interactive area for bounds management
  • Adds a dismiss button to the issue panel for better UX (lines 532-539)
  • Maintains all existing functionality and accessibility features
  • Preserves the drag region for window movement

The nested structure is clear and the interactive area tracking integrates cleanly with the recording controls.


93-101: Verify platform-specific pause capability logic aligns with backend support.

The frontend restricts pause to studio mode, macOS, or Windows+instant mode (lines 93-101). However, the backend has no equivalent platform or mode checks—pause_recording() unconditionally calls recording.pause() for both Instant and Studio types without guards.

Verify one of the following:

  • The pause capability restrictions in the underlying instant_recording and studio_recording implementations justify the frontend constraints, OR
  • The frontend restrictions should be relaxed if the backend genuinely supports pause across all platforms/modes

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
crates/recording/src/output_pipeline/ffmpeg.rs (1)

109-120: Good timestamp monotonicity enforcement.

The logic correctly ensures strictly monotonic timestamps by adjusting any frame with timestamp <= last_ts to last_ts + frame_duration. This prevents muxer issues with non-monotonic or duplicate timestamps.

Consider adding debug logging when timestamp adjustments occur to aid future debugging:

 if let Some(frame_duration) = self.video_frame_duration {
     if let Some(last_ts) = self.last_video_ts
         && timestamp <= last_ts
     {
+        tracing::debug!(
+            "Adjusting timestamp from {:?} to {:?} (last_ts: {:?}, frame_duration: {:?})",
+            timestamp, last_ts + frame_duration, last_ts, frame_duration
+        );
         timestamp = last_ts + frame_duration;
     }

     self.last_video_ts = Some(timestamp);
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b4f25eb and d0d6b10.

📒 Files selected for processing (1)
  • crates/recording/src/output_pipeline/ffmpeg.rs (4 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Format Rust code using rustfmt and ensure all Rust code passes workspace-level clippy lints.
Rust modules should be named with snake_case, and crate directories should be in kebab-case.

Files:

  • crates/recording/src/output_pipeline/ffmpeg.rs
crates/*/src/**/*

📄 CodeRabbit inference engine (AGENTS.md)

Rust crates should place tests within the src/ and/or a sibling tests/ directory for each crate inside crates/*.

Files:

  • crates/recording/src/output_pipeline/ffmpeg.rs
🧠 Learnings (2)
📚 Learning: 2025-10-28T08:39:42.230Z
Learnt from: Brendonovich
Repo: CapSoftware/Cap PR: 1305
File: crates/recording/src/output_pipeline/macos.rs:80-90
Timestamp: 2025-10-28T08:39:42.230Z
Learning: In `crates/recording/src/output_pipeline/macos.rs`, the `AVFoundationMp4Muxer` intentionally holds the `Mutex<MP4Encoder>` lock during retry attempts in `send_video_frame()` and `send_audio_frame()`. This blocking behavior is correct because frame processing must happen sequentially to prevent audio and video frames from being interleaved incorrectly in the encoder.

Applied to files:

  • crates/recording/src/output_pipeline/ffmpeg.rs
📚 Learning: 2025-10-17T05:58:22.586Z
Learnt from: Brendonovich
Repo: CapSoftware/Cap PR: 1219
File: crates/enc-avfoundation/src/mp4.rs:350-373
Timestamp: 2025-10-17T05:58:22.586Z
Learning: In crates/enc-avfoundation/src/mp4.rs, the `finish()` method intentionally skips video extension when `is_paused` is true. This is correct behavior because if recording is paused, the video should not be extended beyond the pause point—the pause is user-initiated, unlike the case where ScreenCaptureKit stops providing frames during static content.

Applied to files:

  • crates/recording/src/output_pipeline/ffmpeg.rs
🧬 Code graph analysis (1)
crates/recording/src/output_pipeline/ffmpeg.rs (2)
crates/enc-avfoundation/src/mp4.rs (1)
  • video_frame_duration (398-411)
crates/enc-ffmpeg/src/video/h264.rs (1)
  • builder (234-236)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Clippy (aarch64-apple-darwin, macos-latest)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (4)
crates/recording/src/output_pipeline/ffmpeg.rs (4)

31-32: LGTM! Fields support timestamp monotonicity enforcement.

The new fields appropriately track frame timing state to enable the timestamp adjustment logic in send_video_frame. Using Option types is correct since video encoding may not always be configured.


51-60: LGTM! Frame duration computation is properly integrated into setup.

The setup logic cleanly handles both configured and unconfigured video scenarios. Computing frame duration at setup time is appropriate since the frame rate should remain constant throughout the recording.


73-74: LGTM! Proper field initialization.

Setting last_video_ts to None is correct since no frames have been sent yet. The first frame will establish the timestamp baseline.


130-138: LGTM! Frame duration calculation is correct.

The calculation matches the reference implementation in crates/enc-avfoundation/src/mp4.rs:397-410. Using .max(1) on both numerator and denominator prevents division by zero while keeping the code simple.

Example verification:

  • 30 FPS: (1 × 1e9 / 30) = 33,333,333 ns ≈ 33.33 ms ✓
  • 60 FPS: (1 × 1e9 / 60) = 16,666,666 ns ≈ 16.67 ms ✓

@richiemcilroy richiemcilroy merged commit 9edf1ce into main Nov 17, 2025
15 of 16 checks passed
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
apps/desktop/src-tauri/src/recording.rs (1)

354-387: Input restoration helper is sound; consider minor cleanups and a semantics check

The overall flow looks good: you avoid restoring while a recording is active, bail when both inputs are already populated, and handle settings/errors defensively.

A couple of refinements:

  • The single‑arm matches on the apply_* calls are a bit clippy‑unfriendly; something like:
-    if let Some(mic) = settings.mic_name.clone().filter(|_| needs_mic) {
-        match apply_mic_input(app.state(), Some(mic)).await {
-            Err(err) => warn!(%err, "Failed to restore microphone input"),
-            Ok(_) => {}
-        }
-    }
+    if let Some(mic) = settings.mic_name.clone().filter(|_| needs_mic) {
+        if let Err(err) = apply_mic_input(app.state(), Some(mic)).await {
+            warn!(%err, "Failed to restore microphone input");
+        }
+    }

(and similarly for camera) will likely keep clippy quieter and simplify the code. As per coding guidelines.

  • Optional: you can avoid cloning when restoration is not needed by checking needs_mic/needs_camera first, then cloning only in that branch, which may help if these IDs ever become heavier types.

Also worth double‑checking that apply_mic_input’s early return on label == current_label won’t prevent restoration in cases where the label is already set but the underlying feed was removed (e.g., after handle_recording_end calls RemoveInput). If that scenario matters for this helper, you may need to revisit the guard in apply_mic_input.

apps/desktop/src-tauri/src/windows.rs (2)

246-302: Hooking idle input restoration from the Main window is sensible; verify scope

Calling restore_recording_inputs_if_idle(app) right after constructing the Main window ties input restoration to the primary UI entry point, which should feel natural to users.

Note that this runs regardless of enable_new_recording_flow; if the intent was to limit this behavior to the new flow only, you might want to gate the call behind if new_recording_flow { ... }. If it’s desirable for both flows, current placement is fine.


802-842: Idle input restoration logic is reasonable; small refactors could improve clarity

The helper correctly:

  • Loads RecordingSettingsStore and logs on error.
  • Short‑circuits when both stored inputs are None.
  • Spawns an async task that first checks is_recording_active_or_pending() to avoid interfering with active/pending recordings.
  • Uses the centralized apply_mic_input / apply_camera_input helpers and logs restoration failures.

A few targeted tweaks to consider:

  • As in recording.rs, the single‑arm match on the apply_* results could be simplified to if let Err(err) = ... to keep clippy happy and reduce noise:
-        if let Some(mic) = mic_name {
-            match apply_mic_input(app_handle.state(), Some(mic)).await {
-                Err(err) => warn!(%err, "Failed to restore microphone input"),
-                Ok(_) => {}
-            }
-        }
+        if let Some(mic) = mic_name {
+            if let Err(err) = apply_mic_input(app_handle.state(), Some(mic)).await {
+                warn!(%err, "Failed to restore microphone input");
+            }
+        }

(and similarly for the camera case). As per coding guidelines.

  • You already guard on is_recording_active_or_pending() once inside the spawned task. If you find in practice that recordings are occasionally started in the brief window after this check, and that causes unwanted re‑attachment while recording, you may want a slightly stronger guard (e.g., rechecking just before each apply_* or having apply_* itself be aware of the “active/pending” concept).

  • As with the other helper, it’s worth confirming apply_mic_input’s label == current_label early return doesn’t prevent re‑attaching a mic that was previously removed but has the same label, since this helper always uses the stored label.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d0d6b10 and c644e6d.

📒 Files selected for processing (2)
  • apps/desktop/src-tauri/src/recording.rs (4 hunks)
  • apps/desktop/src-tauri/src/windows.rs (3 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Format Rust code using rustfmt and ensure all Rust code passes workspace-level clippy lints.
Rust modules should be named with snake_case, and crate directories should be in kebab-case.

Files:

  • apps/desktop/src-tauri/src/windows.rs
  • apps/desktop/src-tauri/src/recording.rs
🧬 Code graph analysis (2)
apps/desktop/src-tauri/src/windows.rs (2)
apps/desktop/src-tauri/src/lib.rs (12)
  • apply_camera_input (428-505)
  • apply_mic_input (337-408)
  • app (1537-1538)
  • app (2597-2597)
  • app (2628-2628)
  • app (2692-2692)
  • app (2698-2698)
  • app (2922-2923)
  • None (3061-3061)
  • app_handle (509-509)
  • app_handle (550-550)
  • app_handle (594-594)
apps/desktop/src-tauri/src/recording_settings.rs (1)
  • get (29-40)
apps/desktop/src-tauri/src/recording.rs (2)
apps/desktop/src-tauri/src/lib.rs (9)
  • apply_camera_input (428-505)
  • apply_mic_input (337-408)
  • app (1537-1538)
  • app (2597-2597)
  • app (2628-2628)
  • app (2692-2692)
  • app (2698-2698)
  • app (2922-2923)
  • None (3061-3061)
apps/desktop/src-tauri/src/recording_settings.rs (1)
  • get (29-40)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Clippy (aarch64-apple-darwin, macos-latest)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (4)
apps/desktop/src-tauri/src/recording.rs (3)

45-66: New imports align with helper usage

The added imports for apply_camera_input, apply_mic_input, and RecordingSettingsStore are consistent with their usage below and keep responsibilities centralized in the helpers. No issues here.


392-398: Restoring inputs before recording start is reasonable

Calling restore_inputs_from_store_if_missing at the top of start_recording ensures missing mic/camera selections are pulled from the store before any further state checks. Given the helper already short‑circuits when a recording is active, this is safe and keeps the command idempotent.


403-417: Camera preview gating for instant recordings looks correct

The new should_open_camera_preview logic:

  • Only runs in Instant mode.
  • Requires a selected camera.
  • Skips if the camera window is already open (so it cooperates with apply_camera_input’s own ShowCapWindow::Camera behavior).

The error handling via map_err(|err| error!(...)).ok() also keeps failures from breaking the recording flow while still logging. No functional issues spotted.

apps/desktop/src-tauri/src/windows.rs (1)

23-30: Import updates correctly reflect new responsibilities

Bringing in apply_camera_input, apply_mic_input, RecordingSettingsStore, and RecordingTargetMode matches the new logic added later in this file. Everything appears consistent with how these symbols are used.

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