Skip to content

Conversation

@Brendonovich
Copy link
Member

@Brendonovich Brendonovich commented Oct 28, 2025

  • Addresses more clippy stuff
  • Improves AVFoundation error handling to wait and retry when encoder is busy
  • Renames 'finish' of encoders to 'flush', since 'finish' is a muxer concept
  • Muxer 'finish' now writes header even if encoder flushing fails, ensuring that resulting mp4s are valid even if incorrect - if muxer finish fails then the file is unrecoverable

Summary by CodeRabbit

  • Bug Fixes

    • Improved encoding reliability with retry loops for transient frame-queueing and clearer per-stream finish/trailer error reporting.
  • API Changes

    • Encoder lifecycle renamed (finish → flush) and now returns errors; muxer finish reports per-stream outcomes. Module visibility and trait signatures updated; audio mixer builder now requires an output channel; tests moved to async.
  • Dependencies

    • Added a retry library and enabled workspace handling for async dev dependency.
  • Chores

    • Stricter lint rule enabled and obsolete test scaffolding removed.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 28, 2025

Walkthrough

Renames many encoder finish APIs to flush with Result returns; consolidates queue-frame errors and adds macOS retrying; exposes several modules publicly; changes Muxer finish to nested Result types; updates recording/audio APIs and tests to async/actor patterns; workspace lint and CI cache branch guards added.

Changes

Cohort / File(s) Change Summary
Workspace & CI
Cargo.toml, .github/workflows/ci.yml, .github/workflows/publish.yml
Added unused_must_use = "deny" to workspace lints and added save-if: ${{ github.ref == 'refs/heads/main' }} guards to Rust cache steps.
enc-avfoundation MP4 & queue
crates/enc-avfoundation/src/mp4.rs
Renamed QueueVideoFrameErrorQueueFrameError (added Construct, NotReadyForMore), queue_video_frame/queue_audio_frame now return Result<(), QueueFrameError>; audio frames borrowed as &frame::Audio; centralized append_sample_buf; added FinishError and finish returning Result.
enc-ffmpeg — audio finish→flush
crates/enc-ffmpeg/src/audio/{aac.rs,audio_encoder.rs,base.rs,mod.rs,opus.rs}
Replaced finish() with flush() on trait and impls; flush returns Result<(), ffmpeg::Error> and calls process_eof; module visibility changed to expose pub mod submodules; implementations updated.
enc-ffmpeg — video/mux visibility & finish results
crates/enc-ffmpeg/src/video/{h264.rs,mod.rs}, crates/enc-ffmpeg/src/mux/{mod.rs,mp4.rs,ogg.rs}, crates/enc-ffmpeg/src/lib.rs
H264 finishflush returning Result; removed some re-exports; mux modules made pub mod; MP4/Ogg finish now return structured FinishResult/FinishError and are idempotent; crate root re-exports audio.
Recording pipeline: finish semantics & retry
crates/recording/src/output_pipeline/{core.rs,ffmpeg.rs,macos.rs,win.rs}
Muxer::finish changed to return nested anyhow::Result<anyhow::Result<()>>; added Deref for AudioFrame; Mp4/Ogg muxers aggregate per-stream results; macOS AVFoundation wraps queue calls in retry (3 attempts, 3ms) retrying NotReadyForMore; Windows muxer adjusted to flush/write trailer with nested result.
Recording deps & audio mixer tests
crates/recording/Cargo.toml, crates/recording/src/sources/audio_mixer.rs
Added dependency retry = "2.1.0"; AudioMixerBuilder::build now accepts output: mpsc::Sender<AudioFrame> and returns Result<AudioMixer, ffmpeg::Error>; tests migrated to async #[tokio::test] and tokio::sync::mpsc, and AudioFrame::new usage.
Examples & actor/async migrations
crates/recording/examples/camera.rs, crates/scap-screencapturekit/examples/cli.rs, crates/scap-ffmpeg/examples/cli.rs
Camera example updated to actor spawn/ask (SetInput, AddSender); screencapturekit macOS example made #[tokio::main] pub async fn main() and simplified callbacks; scap-ffmpeg stop-with-error closure simplified to no-op.
Export/import path & macOS WindowId qualification
crates/export/src/mp4.rs, crates/recording/src/{instant_recording.rs,lib.rs,studio_recording.rs}
Updated imports to explicit submodules (e.g., cap_enc_ffmpeg::aac::AACEncoder) and qualified macOS WindowId usages to scap_targets::WindowId for fields and builder APIs.
enc-mediafoundation: module & removal
crates/enc-mediafoundation/src/{lib.rs,unsafe_send.rs,video/h264.rs}
Promoted mft to pub mod; removed unsafe_send.rs (deleted UnsafeSend<T> API); removed first_time field from H264Encoder.
Minor tidy / tracing / imports
crates/camera-directshow/src/lib.rs, crates/camera-windows/src/lib.rs, crates/mediafoundation-ffmpeg/src/h264.rs
Switched some tracing imports to wildcard; removed unused imports (e.g., Instant).
Tests / small signature & build tweaks
crates/audio/src/latency.rs, crates/rendering/src/zoom.rs, crates/timestamp/src/macos.rs, crates/scap-screencapturekit/Cargo.toml
Added macOS-specific latency fallback constant and test lint allow; tightened test lifetimes; removed a macOS test; added tokio.workspace = true under dev-deps.
Windows capture internals
crates/recording/src/sources/screen_capture/windows.rs
Internal cleanup: removed unused symbols, introduced CancellationToken for cancelable logging task, removed legacy SourceError/CapturerHandle; internal reorganization only.
Apps UI minor
apps/desktop/src-tauri/src/target_select_overlay.rs
Ignored return values of SetWindowPlacement/ShowWindow/SetForegroundWindow by assigning to _.

Sequence Diagram(s)

sequenceDiagram
    participant Caller
    participant Muxer
    participant VideoEnc as VideoEncoder
    participant AudioEnc as AudioEncoder
    participant Writer as TrailerWriter

    Note left of Caller: New high-level finish/flush flow
    Caller->>Muxer: finish(timestamp) -> Result<FinishResult, FinishError>
    Muxer->>VideoEnc: flush(output) -> Result<(), ffmpeg::Error>
    VideoEnc-->>Muxer: Ok / Err
    Muxer->>AudioEnc: flush(output) -> Result<(), ffmpeg::Error>
    AudioEnc-->>Muxer: Ok / Err
    Muxer->>Writer: write_trailer() -> Result<(), ffmpeg::Error>
    Writer-->>Muxer: Ok / Err
    Muxer-->>Caller: Ok(FinishResult{video_result,audio_result}) or Err(FinishError)
Loading
sequenceDiagram
    participant AV as AVFoundationMp4Muxer
    participant MP4 as MP4Encoder
    participant Caller

    Note left of AV: macOS queue retry loop (3 attempts, 3ms)
    Caller->>AV: send_video_frame(frame)
    AV->>MP4: queue_video_frame(frame) [try 1]
    alt NotReadyForMore
        MP4-->>AV: NotReadyForMore
        AV->>MP4: queue_video_frame(frame) [try 2 after 3ms]
        MP4-->>AV: Ok / Err
    end
    AV-->>Caller: Ok / Err (mapped with context)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Areas needing careful review:

  • crates/enc-avfoundation/src/mp4.rs — public error rename, new variants, borrowed audio param, append_sample_buf and finish semantics.
  • crates/enc-ffmpeg — trait rename finish→flush across audio/video and changed return types; module visibility changes.
  • crates/recording/src/output_pipeline/* — Muxer finish signature change to nested Result, macOS retry integration and error mapping.
  • crates/enc-mediafoundation/src/unsafe_send.rs removal — ensure no downstream usages remain.

Possibly related PRs

Poem

🐰 I nudged a lint and hopped the queue,

I told finish to flush and retry a few,
Actors whisper, async rivers hum,
Errors speak clearly — now we hear some drum,
Tiny rabbit hops — the code rolls on anew!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.36% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "Encoder error handling improvements" accurately reflects the central theme of the changeset. The PR introduces significant refactoring across the encoder implementations, including renaming finish methods to flush, creating new error types like QueueFrameError and FinishError for better error propagation, implementing retry logic with exponential backoff for AVFoundation encoding operations, and adding Clippy lint enforcement. These changes are cohesively focused on improving how errors are handled within the encoding pipeline, making the title a clear and specific summary of the primary work.
✨ 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 handle-all-recording-errors

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: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
crates/recording/examples/camera.rs (2)

32-36: Remove no-op property calls and guard the receive with a timeout.

The unused calls likely trigger clippy’s unused_must_use. Add a timeout to avoid hanging when no frames arrive.

-    let frame = rx.recv_async().await.unwrap().inner;
-    frame.format();
-    frame.width();
-    frame.height();
+    use tokio::time::{timeout, Duration};
+    let frame = timeout(Duration::from_secs(5), rx.recv_async())
+        .await
+        .expect("timed out waiting for a camera frame")
+        .expect("frame channel closed")
+        .inner;

47-59: Do not assume contiguous RGB24; respect linesize/stride when encoding JPEG.

FFmpeg frames can have padding per row. Slicing data(0)[0..(w*h*3)] risks corrupted output on aligned buffers. Pack rows using linesize(0) before encoding.

-    println!(
-        "Converted frame data len: {}",
-        converted_frame.data(0).len()
-    );
-    let mut file = std::fs::File::create("./out.jpeg").unwrap();
-    jpeg::JpegEncoder::new(&mut file)
-        .encode(
-            &converted_frame.data(0)[0..(frame.width() * frame.height() * 3) as usize],
-            frame.width(),
-            frame.height(),
-            ColorType::Rgb8.into(),
-        )
-        .unwrap();
+    let (w, h) = (converted_frame.width(), converted_frame.height());
+    let row_bytes = (w * 3) as usize;
+    let line_size = converted_frame.linesize(0) as usize;
+    let src = converted_frame.data(0);
+    let mut packed = Vec::with_capacity(row_bytes * h as usize);
+    for y in 0..h as usize {
+        let start = y * line_size;
+        let end = start + row_bytes;
+        packed.extend_from_slice(&src[start..end]);
+    }
+    println!("Packed RGB size: {}", packed.len());
+    let mut file = std::fs::File::create("./out.jpeg").expect("create out.jpeg");
+    jpeg::JpegEncoder::new(&mut file)
+        .encode(&packed, w, h, ColorType::Rgb8.into())
+        .expect("JPEG encode failed");
crates/export/src/mp4.rs (1)

75-95: Bug: base is moved into the spawn_blocking closure, then used later

The move || captures base due to base.output_path usage, causing use-after-move. Capture only output_path instead.

Apply:

-        let encoder_thread = tokio::task::spawn_blocking(move || {
+        let output_path_cloned = output_path.clone();
+        let encoder_thread = tokio::task::spawn_blocking(move || {
@@
-            let mut encoder = MP4File::init(
-                "output",
-                base.output_path.clone(),
+            let mut encoder = MP4File::init(
+                "output",
+                output_path_cloned.clone(),
@@
-            Ok::<_, String>(base.output_path)
+            Ok::<_, String>(output_path_cloned)

Also applies to: 118-118

crates/enc-ffmpeg/src/audio/aac.rs (1)

33-35: Bitrate constant/comment mismatch

320_000 != “128k”. Fix either the value or the comment.

-    const OUTPUT_BITRATE: usize = 320 * 1000; // 128k
+    // 128 kbps
+    const OUTPUT_BITRATE: usize = 128 * 1000;
crates/enc-ffmpeg/src/video/h264.rs (1)

296-310: Remove invalid encoder option "vsync" at line 307

"vsync" is not a libx264 encoder option—it's a muxer/FFmpeg CLI-level setting. Passing it via the encoder's AVDictionary will be ignored or cause errors. Proper timestamp synchronization is already handled by the existing set_time_base(), set_frame_rate(), and stream rate configuration (lines 181, 199–200). Delete line 307.

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

126-149: Fix the associated type mismatch in OggMuxer::finish.

The Finish type is declared as Result<(), ffmpeg::Error> but the implementation returns anyhow::Result<()> via the ? operator in Ok(self.0.finish()?), which flattens the nested Result. This creates a type inconsistency where the declared type suggests a nested Result but the actual return is flat.

Either:

  1. Change type Finish = (); to match what's actually returned, or
  2. Properly return nested Result: self.0.finish().map_err(|e| anyhow!(e))

The current pattern works because callers treat it as a flat Result, but the misleading type signature should be corrected.

🧹 Nitpick comments (10)
crates/scap-ffmpeg/examples/cli.rs (1)

75-75: Consider documenting why errors are ignored or removing the callback.

The empty callback |_, _| {} silently ignores all stop-with-error events. Since this is an example file that users may reference, consider either:

  1. Adding a comment explaining that errors are now handled internally by the builder, or
  2. Removing the .with_stop_with_err_cb() call entirely if it's no longer necessary.

This would better demonstrate best practices for users of the library.

Example with documentation:

+        // Errors are handled internally by the capturer; custom callback not needed
         .with_stop_with_err_cb(|_, _| {})

Or if the callback is optional, consider removing it:

-        .with_stop_with_err_cb(|_, _| {})
         .build()
crates/recording/examples/camera.rs (3)

14-14: Make type inference explicit and handle “no cameras” early.

Avoid potential inference hiccups and provide a clearer UX when zero devices are found.

-    let cameras = cap_camera::list_cameras().map(CameraSelection).collect();
+    let cameras: Vec<_> = cap_camera::list_cameras().map(CameraSelection).collect();
+    if cameras.is_empty() {
+        eprintln!("No cameras found.");
+        return;
+    }

19-27: Double-await is fine, but replace unwrap with propagation or expect.

Prevent example panics and improve diagnostics.

-    feed.ask(camera::SetInput {
-        id: DeviceOrModelID::from_info(&device.0),
-    })
-    .await
-    .unwrap()
-    .await
-    .unwrap();
+    feed.ask(camera::SetInput {
+        id: DeviceOrModelID::from_info(&device.0),
+    })
+    .await
+    .expect("failed to send SetInput to CameraFeed")
+    .await
+    .expect("CameraFeed failed to apply selected device");

Optional: switch async fn main() -> anyhow::Result<()> and use ? instead.


28-31: Avoid potential hangs; add a timeout and consider a slightly larger channel.

A bounded(1) mailbox plus a slow consumer can block the producer. Add a timeout to first-frame wait to avoid indefinite await if the camera/encoder stalls.

-    let (tx, rx) = flume::bounded(1);
+    let (tx, rx) = flume::bounded(2); // small buffer to reduce backpressure
@@
-    feed.ask(camera::AddSender(tx)).await.unwrap();
+    feed.ask(camera::AddSender(tx))
+        .await
+        .expect("failed to add frame sender");

And receive with timeout below (see next comment).

crates/scap-screencapturekit/examples/cli.rs (2)

37-41: Output callback simplification is fine

Ignoring frames is acceptable for the example; consider leaving a commented log for quick debugging.


42-42: Don’t swallow stop errors; at least log them

Swallowing stream/error hides failures during demos. Minimal visibility helps troubleshooting.

Apply:

-        .with_stop_with_err_cb(|_, _| {})
+        .with_stop_with_err_cb(|stream, err| {
+            eprintln!("capture stopped: stream={:?}, err={:?}", stream, err);
+        })

As per coding guidelines.

crates/recording/src/output_pipeline/win.rs (1)

278-278: Flush errors are silently ignored.

The audio encoder's flush() error is explicitly discarded. While the PR description mentions continuing even if flushing fails, silent failure could make debugging difficult.

Consider logging the error:

-            let _ = audio_encoder.flush(&mut output);
+            if let Err(e) = audio_encoder.flush(&mut output) {
+                tracing::error!("Failed to flush audio encoder: {}", e);
+            }
crates/enc-ffmpeg/src/audio/aac.rs (1)

117-119: Avoid confusion between inherent and trait flush

Be explicit to call the inherent method from the trait impl.

-    fn flush(&mut self, output: &mut format::context::Output) -> Result<(), ffmpeg::Error> {
-        self.flush(output)
-    }
+    fn flush(&mut self, output: &mut format::context::Output) -> Result<(), ffmpeg::Error> {
+        AACEncoder::flush(self, output)
+    }
crates/recording/src/output_pipeline/macos.rs (2)

97-106: Mirror the video retry improvements for audio

Apply the same lock-scoping/backoff/jitter/metric suggestions here to avoid blocking video when audio is busy, and vice versa.


89-90: Include timestamp in error context for faster triage

Add the timestamp to anyhow messages.

-        .map_err(|e| anyhow!("send_video_frame/{e}"))
+        .map_err(|e| anyhow!("send_video_frame ts={:?} / {e}", timestamp))
@@
-        .map_err(|e| anyhow!("send_audio_frame/{e}"))
+        .map_err(|e| anyhow!("send_audio_frame ts={:?} / {e}", timestamp))

Also applies to: 106-107

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a133751 and c6c392f.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (27)
  • Cargo.toml (1 hunks)
  • crates/audio/src/latency.rs (1 hunks)
  • crates/enc-avfoundation/src/mp4.rs (9 hunks)
  • crates/enc-ffmpeg/src/audio/aac.rs (2 hunks)
  • crates/enc-ffmpeg/src/audio/audio_encoder.rs (1 hunks)
  • crates/enc-ffmpeg/src/audio/base.rs (1 hunks)
  • crates/enc-ffmpeg/src/audio/mod.rs (1 hunks)
  • crates/enc-ffmpeg/src/audio/opus.rs (3 hunks)
  • crates/enc-ffmpeg/src/lib.rs (1 hunks)
  • crates/enc-ffmpeg/src/mux/mod.rs (1 hunks)
  • crates/enc-ffmpeg/src/mux/mp4.rs (4 hunks)
  • crates/enc-ffmpeg/src/mux/ogg.rs (2 hunks)
  • crates/enc-ffmpeg/src/video/h264.rs (1 hunks)
  • crates/enc-ffmpeg/src/video/mod.rs (0 hunks)
  • crates/export/src/mp4.rs (2 hunks)
  • crates/recording/Cargo.toml (1 hunks)
  • crates/recording/examples/camera.rs (1 hunks)
  • crates/recording/src/output_pipeline/core.rs (4 hunks)
  • crates/recording/src/output_pipeline/ffmpeg.rs (7 hunks)
  • crates/recording/src/output_pipeline/macos.rs (4 hunks)
  • crates/recording/src/output_pipeline/win.rs (1 hunks)
  • crates/recording/src/sources/audio_mixer.rs (3 hunks)
  • crates/rendering/src/zoom.rs (1 hunks)
  • crates/scap-ffmpeg/examples/cli.rs (1 hunks)
  • crates/scap-screencapturekit/Cargo.toml (1 hunks)
  • crates/scap-screencapturekit/examples/cli.rs (2 hunks)
  • crates/timestamp/src/macos.rs (0 hunks)
💤 Files with no reviewable changes (2)
  • crates/timestamp/src/macos.rs
  • crates/enc-ffmpeg/src/video/mod.rs
🧰 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/win.rs
  • crates/enc-ffmpeg/src/audio/audio_encoder.rs
  • crates/rendering/src/zoom.rs
  • crates/scap-screencapturekit/examples/cli.rs
  • crates/recording/src/sources/audio_mixer.rs
  • crates/recording/src/output_pipeline/macos.rs
  • crates/enc-ffmpeg/src/video/h264.rs
  • crates/audio/src/latency.rs
  • crates/enc-ffmpeg/src/audio/base.rs
  • crates/enc-ffmpeg/src/audio/aac.rs
  • crates/enc-ffmpeg/src/mux/mod.rs
  • crates/enc-ffmpeg/src/mux/ogg.rs
  • crates/recording/examples/camera.rs
  • crates/enc-ffmpeg/src/mux/mp4.rs
  • crates/recording/src/output_pipeline/core.rs
  • crates/scap-ffmpeg/examples/cli.rs
  • crates/recording/src/output_pipeline/ffmpeg.rs
  • crates/enc-ffmpeg/src/audio/mod.rs
  • crates/enc-avfoundation/src/mp4.rs
  • crates/export/src/mp4.rs
  • crates/enc-ffmpeg/src/lib.rs
  • crates/enc-ffmpeg/src/audio/opus.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/win.rs
  • crates/enc-ffmpeg/src/audio/audio_encoder.rs
  • crates/rendering/src/zoom.rs
  • crates/recording/src/sources/audio_mixer.rs
  • crates/recording/src/output_pipeline/macos.rs
  • crates/enc-ffmpeg/src/video/h264.rs
  • crates/audio/src/latency.rs
  • crates/enc-ffmpeg/src/audio/base.rs
  • crates/enc-ffmpeg/src/audio/aac.rs
  • crates/enc-ffmpeg/src/mux/mod.rs
  • crates/enc-ffmpeg/src/mux/ogg.rs
  • crates/enc-ffmpeg/src/mux/mp4.rs
  • crates/recording/src/output_pipeline/core.rs
  • crates/recording/src/output_pipeline/ffmpeg.rs
  • crates/enc-ffmpeg/src/audio/mod.rs
  • crates/enc-avfoundation/src/mp4.rs
  • crates/export/src/mp4.rs
  • crates/enc-ffmpeg/src/lib.rs
  • crates/enc-ffmpeg/src/audio/opus.rs
🧠 Learnings (1)
📚 Learning: 2025-10-17T05:58:22.586Z
Learnt from: Brendonovich
PR: CapSoftware/Cap#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/macos.rs
🧬 Code graph analysis (13)
crates/enc-ffmpeg/src/audio/audio_encoder.rs (4)
crates/enc-ffmpeg/src/audio/aac.rs (2)
  • flush (107-109)
  • flush (117-119)
crates/enc-ffmpeg/src/audio/base.rs (1)
  • flush (39-45)
crates/enc-ffmpeg/src/audio/opus.rs (2)
  • flush (101-103)
  • flush (119-121)
crates/enc-ffmpeg/src/audio/buffered_resampler.rs (2)
  • flush (264-271)
  • output (54-56)
crates/scap-screencapturekit/examples/cli.rs (1)
crates/scap-screencapturekit/src/capture.rs (1)
  • builder (78-87)
crates/recording/src/sources/audio_mixer.rs (1)
crates/recording/src/output_pipeline/core.rs (5)
  • new (619-621)
  • new (662-664)
  • new (695-697)
  • new (739-756)
  • timestamp (782-782)
crates/recording/src/output_pipeline/macos.rs (2)
crates/recording/src/output_pipeline/core.rs (2)
  • finish (802-802)
  • timestamp (782-782)
crates/recording/src/output_pipeline/ffmpeg.rs (3)
  • finish (74-93)
  • finish (148-150)
  • timestamp (22-24)
crates/enc-ffmpeg/src/video/h264.rs (4)
crates/enc-ffmpeg/src/audio/aac.rs (2)
  • flush (107-109)
  • flush (117-119)
crates/enc-ffmpeg/src/audio/audio_encoder.rs (1)
  • flush (12-12)
crates/enc-ffmpeg/src/audio/base.rs (1)
  • flush (39-45)
crates/enc-ffmpeg/src/audio/opus.rs (2)
  • flush (101-103)
  • flush (119-121)
crates/enc-ffmpeg/src/audio/base.rs (4)
crates/enc-ffmpeg/src/audio/aac.rs (2)
  • flush (107-109)
  • flush (117-119)
crates/enc-ffmpeg/src/audio/audio_encoder.rs (1)
  • flush (12-12)
crates/enc-ffmpeg/src/audio/opus.rs (2)
  • flush (101-103)
  • flush (119-121)
crates/enc-ffmpeg/src/audio/buffered_resampler.rs (2)
  • flush (264-271)
  • output (54-56)
crates/enc-ffmpeg/src/audio/aac.rs (4)
crates/enc-ffmpeg/src/audio/audio_encoder.rs (1)
  • flush (12-12)
crates/enc-ffmpeg/src/audio/base.rs (1)
  • flush (39-45)
crates/enc-ffmpeg/src/audio/opus.rs (2)
  • flush (101-103)
  • flush (119-121)
crates/enc-ffmpeg/src/audio/buffered_resampler.rs (2)
  • flush (264-271)
  • output (54-56)
crates/enc-ffmpeg/src/mux/ogg.rs (3)
crates/enc-ffmpeg/src/mux/mp4.rs (2)
  • finish (111-144)
  • drop (156-158)
crates/recording/src/output_pipeline/ffmpeg.rs (2)
  • finish (74-93)
  • finish (148-150)
crates/recording/src/output_pipeline/win.rs (1)
  • finish (275-281)
crates/recording/examples/camera.rs (3)
apps/cli/src/main.rs (1)
  • cap_camera (113-113)
apps/desktop/src-tauri/src/recording.rs (1)
  • list_cameras (208-210)
crates/recording/src/feeds/camera.rs (1)
  • from_info (140-144)
crates/enc-ffmpeg/src/mux/mp4.rs (4)
crates/enc-avfoundation/src/mp4.rs (1)
  • finish (440-481)
crates/enc-ffmpeg/src/mux/ogg.rs (1)
  • finish (48-61)
crates/recording/src/output_pipeline/ffmpeg.rs (2)
  • finish (74-93)
  • finish (148-150)
crates/mediafoundation-ffmpeg/src/h264.rs (1)
  • finish (151-163)
crates/recording/src/output_pipeline/core.rs (8)
crates/recording/src/feeds/microphone.rs (1)
  • deref (255-257)
crates/recording/src/feeds/camera.rs (1)
  • deref (120-122)
crates/enc-ffmpeg/src/mux/mp4.rs (1)
  • finish (111-144)
crates/enc-ffmpeg/src/mux/ogg.rs (1)
  • finish (48-61)
crates/recording/src/output_pipeline/ffmpeg.rs (3)
  • finish (74-93)
  • finish (148-150)
  • timestamp (22-24)
crates/recording/src/output_pipeline/macos.rs (1)
  • finish (55-61)
crates/recording/src/output_pipeline/win.rs (1)
  • finish (275-281)
crates/mediafoundation-ffmpeg/src/h264.rs (1)
  • finish (151-163)
crates/recording/src/output_pipeline/ffmpeg.rs (4)
crates/enc-ffmpeg/src/mux/mp4.rs (1)
  • finish (111-144)
crates/enc-ffmpeg/src/mux/ogg.rs (1)
  • finish (48-61)
crates/recording/src/output_pipeline/core.rs (2)
  • finish (802-802)
  • timestamp (782-782)
crates/mediafoundation-ffmpeg/src/h264.rs (1)
  • finish (151-163)
crates/enc-ffmpeg/src/audio/opus.rs (4)
crates/enc-ffmpeg/src/audio/aac.rs (4)
  • flush (107-109)
  • flush (117-119)
  • send_frame (98-105)
  • send_frame (113-115)
crates/enc-ffmpeg/src/audio/audio_encoder.rs (2)
  • flush (12-12)
  • send_frame (11-11)
crates/enc-ffmpeg/src/audio/base.rs (2)
  • flush (39-45)
  • send_frame (21-37)
crates/enc-ffmpeg/src/audio/buffered_resampler.rs (2)
  • flush (264-271)
  • output (54-56)
⏰ 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). (4)
  • GitHub Check: Clippy
  • GitHub Check: Build Desktop (x86_64-pc-windows-msvc, windows-latest)
  • GitHub Check: Build Desktop (aarch64-apple-darwin, macos-latest)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (39)
crates/rendering/src/zoom.rs (1)

243-245: LGTM! Explicit lifetime annotation improves clarity.

The explicit lifetime parameter makes the borrowing relationship between the input slice and returned SegmentsCursor clear and likely addresses a Clippy lint.

crates/audio/src/latency.rs (1)

618-618: LGTM! Appropriate suppression for test code.

The unchecked_duration_subtraction lint suppression is reasonable for this test module. The tests use small, known durations (60-300ms) when backdating Instant values for test setup, which are safe operations in controlled test environments.

crates/recording/examples/camera.rs (3)

1-4: Imports look good.

Module paths and re-exports are idiomatic; no issues.


7-8: LGTM on new imports.

Bringing in kameo::Actor and Display is appropriate for the actor flow and selection wrapper.


62-68: Display impl is clean.

Using CameraInfo::display_name() keeps the prompt user-friendly.

crates/scap-screencapturekit/examples/cli.rs (1)

8-8: Import of cidre::sc looks good

Required for ShareableContent; consistent with other examples.

crates/scap-screencapturekit/Cargo.toml (1)

24-24: LGTM!

The addition of tokio as a dev-dependency is appropriate for enabling async capabilities in examples and tests.

Cargo.toml (1)

67-67: LGTM!

Adding unused_must_use = "deny" is a good practice that ensures Results and other must-use types are properly handled across the workspace, which complements the error handling improvements in this PR.

crates/enc-avfoundation/src/mp4.rs (3)

51-60: LGTM!

The consolidation of frame queuing errors into a unified QueueFrameError enum improves consistency. The new variants Construct and NotReadyForMore provide clearer error semantics for distinguishing transient busy states from fatal errors.


217-220: Good addition: Early detection of busy encoder.

The check for is_ready_for_more_media_data() before queuing enables callers to detect when the encoder is busy and implement retry logic, which aligns with the PR's improved AVFoundation error handling.


615-623: No changes needed — handling of Ok(false) is correct.

Returning false from appendSampleBuffer just means that particular sample was not accepted and does not always mean the writer has failed. Per Apple documentation, you must check AVAssetWriter.status; if status is .failed then AVAssetWriter.error will explain why, otherwise the failure can be transient or due to the sample/input state.

Your code correctly implements this: it checks writer.status() == Failed after append_sample_buf returns Ok(false) and only returns an error if the writer has truly failed. Otherwise, it allows the function to return Ok(()), treating the rejection as a transient condition (backpressure, format/timing mismatch, etc.) that will be retried or resolved by caller logic.

crates/recording/Cargo.toml (1)

51-51: LGTM!

The addition of the retry crate supports the implementation of retry logic for handling transient encoder busy states, as described in the PR objectives.

crates/enc-ffmpeg/src/lib.rs (1)

2-4: LGTM!

The module reorganization and addition of pub use audio::*; makes the audio module's public API accessible from the crate root, consistent with the existing patterns for video and mux modules.

crates/enc-ffmpeg/src/audio/base.rs (1)

39-45: LGTM!

The rename from finish to flush more accurately describes the operation (flushing buffered frames), and the addition of process_eof ensures proper encoder finalization with error propagation.

crates/enc-ffmpeg/src/audio/audio_encoder.rs (1)

12-12: LGTM!

The rename to flush and the addition of Result return type enables proper error propagation during encoder finalization, aligning with FFmpeg conventions where "flush" indicates draining buffered frames.

crates/enc-ffmpeg/src/audio/aac.rs (1)

107-109: Flush rename aligns with base; good

Delegation to base.flush with Result is correct.

crates/recording/src/output_pipeline/macos.rs (1)

55-61: Finish signature change acknowledged

Returning Ok(()) as associated Finish keeps Muxer uniform. No issues.

crates/export/src/mp4.rs (1)

3-3: Import paths are valid and require no changes

The cap_enc_ffmpeg crate properly re-exports all modules at the crate root via wildcard statements:

  • pub use audio::*; makes aac::AACEncoder accessible
  • pub use video::*; makes h264::H264Encoder accessible
  • pub use mux::*; makes mp4::* accessible
  • AudioEncoder is exposed through the audio re-export chain

The import statement at line 3 of crates/export/src/mp4.rs is correct and does not require alternative paths.

Likely an incorrect or invalid review comment.

crates/enc-ffmpeg/src/video/h264.rs (1)

265-266: Rename to flush with error propagation confirmed complete

Verification confirms all H264Encoder::finish call sites have been updated to flush. The method is properly called at crates/enc-ffmpeg/src/mux/mp4.rs:120 with correct error propagation pattern matching audio encoders. No orphaned finish references remain in the codebase.

crates/enc-ffmpeg/src/audio/mod.rs (1)

4-9: Visibility reorganization verified; changes are safe and backward-compatible

The module visibility changes are coherent:

  • base kept private—no code imports it (verified safe)
  • buffered_resampler, aac, opus are public—aligns with new API intent
  • Glob re-export at lib.rs ensures backward compatibility; callers can use both cap_enc_ffmpeg::audio::opus and cap_enc_ffmpeg::opus
  • Existing internal imports (e.g., crate::audio::opus in mux/ogg.rs) continue to work unchanged

No breaking changes detected.

crates/enc-ffmpeg/src/mux/mod.rs (1)

1-2: Public submodules are an implementation detail; no breaking change here

The crate already re-exports mux contents at the root level via pub use mux::*; in lib.rs. Making pub mod mp4/ogg adds an alternative path but doesn't change existing imports (cap_enc_ffmpeg::mp4, cap_enc_ffmpeg::ogg). No breaking change occurs, and compat re-exports are unnecessary.

Likely an incorrect or invalid review comment.

crates/recording/src/sources/audio_mixer.rs (1)

482-682: LGTM! Test migration to async is well-executed.

The test suite has been successfully migrated from synchronous flume channels to asynchronous tokio mpsc channels. All test assertions remain unchanged, and the async operations (.send().await, .next(), .try_next()) are correctly applied.

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

6-6: Good practice: Narrow imports improve clarity.

Replacing the glob import with explicit encoder imports makes dependencies clearer and reduces namespace pollution.


33-93: Improved error handling: Per-stream results are now captured.

The new Finish struct enables callers to inspect individual encoder flush results while ensuring the trailer is always written (unless trailer write itself fails). This aligns with the PR objective: "Muxer finish now writes the header even if encoder flushing fails, ensuring resulting MP4 files are valid."


105-105: Error propagation added for video frame queueing.

Good improvement—encoder errors are now properly surfaced to the caller.


115-115: Error propagation added for audio frame sending.

Consistent with the video path; encoder errors are now properly surfaced.

crates/enc-ffmpeg/src/audio/opus.rs (2)

11-13: Import update aligns with trait implementation.

The addition of AudioEncoder to imports supports the new trait implementation below.


101-122: Renamed finish→flush and added AudioEncoder trait impl.

The method rename from finish to flush correctly reflects muxer vs. encoder semantics, as noted in the PR description. The AudioEncoder trait implementation is consistent with the AAC encoder pattern (using Duration::MAX as a placeholder timestamp).

crates/enc-ffmpeg/src/mux/mp4.rs (4)

4-4: Import simplified to glob import.

The change from specific imports to tracing::* is acceptable for this crate's usage pattern.


9-9: Import path updated for H264Encoder.

The module path change aligns with the refactored encoder module structure.


31-42: Well-structured error and result types.

FinishError and FinishResult provide clear separation between muxer-level failures and per-stream encoder results.


157-157: Drop safely ignores finish errors.

This is appropriate for Drop implementations to avoid panics.

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

16-16: Import added for Deref implementation.

Supports the new Deref impl for AudioFrame below.


700-706: Deref implementation improves ergonomics.

Allowing AudioFrame to dereference to the inner ffmpeg::frame::Audio simplifies access patterns. The implementation follows the same pattern as other source types in the codebase.


787-802: Associated Finish type enables flexible error reporting.

The addition of type Finish and the updated finish signature allow different muxer implementations to return appropriate finish results—from simple unit returns to detailed per-stream encoder results. This is a well-designed abstraction.

crates/enc-ffmpeg/src/mux/ogg.rs (4)

4-4: Import path updated for OpusEncoder.

Aligns with the refactored audio module structure.


12-18: Clear error type for finish failures.

FinishError provides explicit variants for muxer-level failures, making error handling more predictable.


48-61: Finish implementation correctly handles errors.

The implementation properly:

  • Guards against double-finish with AlreadyFinished
  • Captures encoder flush results
  • Propagates trailer write failures via WriteTrailerFailed
  • Returns the encoder flush result for caller inspection

The nested Result<Result<...>, ...> structure distinguishes muxer-level failures from encoder-level issues, which aligns with the PR's error handling improvements.


66-66: Drop safely ignores finish errors.

Appropriate for Drop implementations to avoid panics.

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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/recording/src/sources/screen_capture/windows.rs (1)

26-28: Replace hardcoded duration with LOG_INTERVAL constant, and remove or clarify unused constants.

Verified findings:

  • Line 251 hardcodes Duration::from_secs(5) instead of using LOG_INTERVAL (defined at line 27)
  • WINDOW_DURATION (line 26) has no usages in this file
  • MAX_DROP_RATE_THRESHOLD (line 28) has no usages in this file

Apply this fix:

 tokio_rt.spawn(
     async move {
         loop {
-            tokio::time::sleep(Duration::from_secs(5)).await;
+            tokio::time::sleep(LOG_INTERVAL).await;
             debug!(
                 "Captured {} frames",
                 video_frame_counter.load(atomic::Ordering::Relaxed)

Remove WINDOW_DURATION and MAX_DROP_RATE_THRESHOLD from lines 26 and 28, or add documentation if they're intended for future use.

🧹 Nitpick comments (4)
crates/mediafoundation-ffmpeg/src/h264.rs (1)

3-3: Consider using explicit imports instead of wildcard.

The file only uses the info! macro from the tracing crate. Using an explicit import (use tracing::info;) would make the dependencies clearer and align with Rust best practices, which generally discourage wildcard imports for maintainability.

Apply this diff to use an explicit import:

-use tracing::*;
+use tracing::info;
crates/camera-directshow/src/lib.rs (1)

13-13: Consider using explicit imports instead of wildcard.

The file only uses the trace! macro from the tracing crate. Using an explicit import (use tracing::trace;) would make the dependencies clearer and align with Rust best practices, which generally discourage wildcard imports for maintainability.

Apply this diff to use an explicit import:

-use tracing::*;
+use tracing::trace;
crates/audio/src/latency.rs (1)

619-619: Consider using checked_sub().expect() for explicit test setup.

The module-level suppression of clippy::unchecked_duration_subtraction is acceptable for test code where the operations are intentional test setup. However, for better clarity and error messages, consider using checked_sub().expect() instead:

-estimator.last_update_at = Some(Instant::now() - Duration::from_millis(60));
+estimator.last_update_at = Some(
+    Instant::now()
+        .checked_sub(Duration::from_millis(60))
+        .expect("test duration too large")
+);

This makes the intent explicit and provides clearer panic messages if the operation fails. That said, the current approach with #[allow] is acceptable for this test module.

crates/recording/src/output_pipeline/win.rs (1)

280-291: Consider adding logging for better observability.

The implementation correctly captures the audio encoder flush result and propagates trailer write errors, aligning with the PR objectives. However, for consistency with other muxers (e.g., mp4.rs) and improved observability, consider adding tracing logs when flushing the audio encoder and writing the trailer.

Apply this diff to add logging:

 fn finish(&mut self, _: Duration) -> anyhow::Result<Self::Finish> {
     let mut output = self.output.lock()?;
-    let audio_result = self
-        .audio_encoder
-        .as_mut()
-        .map(|enc| enc.flush(&mut output))
-        .unwrap_or(Ok(()));
+    let audio_result = self
+        .audio_encoder
+        .as_mut()
+        .map(|enc| {
+            tracing::info!("WindowsMuxer: Flushing audio encoder");
+            enc.flush(&mut output).inspect_err(|e| {
+                tracing::error!("Failed to flush audio encoder: {e:#}");
+            })
+        })
+        .unwrap_or(Ok(()));
 
+    tracing::info!("WindowsMuxer: Writing trailer");
     output.write_trailer()?;
 
     Ok(Finish { audio_result })
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ff06c5f and f4ba429.

📒 Files selected for processing (14)
  • crates/audio/src/latency.rs (2 hunks)
  • crates/camera-directshow/src/lib.rs (1 hunks)
  • crates/camera-windows/src/lib.rs (1 hunks)
  • crates/enc-avfoundation/src/mp4.rs (9 hunks)
  • crates/enc-mediafoundation/src/lib.rs (1 hunks)
  • crates/enc-mediafoundation/src/unsafe_send.rs (0 hunks)
  • crates/enc-mediafoundation/src/video/h264.rs (0 hunks)
  • crates/mediafoundation-ffmpeg/src/h264.rs (1 hunks)
  • crates/recording/src/instant_recording.rs (2 hunks)
  • crates/recording/src/lib.rs (2 hunks)
  • crates/recording/src/output_pipeline/win.rs (4 hunks)
  • crates/recording/src/sources/screen_capture/macos.rs (1 hunks)
  • crates/recording/src/sources/screen_capture/mod.rs (0 hunks)
  • crates/recording/src/sources/screen_capture/windows.rs (1 hunks)
💤 Files with no reviewable changes (3)
  • crates/recording/src/sources/screen_capture/mod.rs
  • crates/enc-mediafoundation/src/video/h264.rs
  • crates/enc-mediafoundation/src/unsafe_send.rs
🧰 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/camera-windows/src/lib.rs
  • crates/camera-directshow/src/lib.rs
  • crates/recording/src/instant_recording.rs
  • crates/audio/src/latency.rs
  • crates/mediafoundation-ffmpeg/src/h264.rs
  • crates/enc-mediafoundation/src/lib.rs
  • crates/recording/src/lib.rs
  • crates/enc-avfoundation/src/mp4.rs
  • crates/recording/src/sources/screen_capture/macos.rs
  • crates/recording/src/output_pipeline/win.rs
  • crates/recording/src/sources/screen_capture/windows.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/camera-windows/src/lib.rs
  • crates/camera-directshow/src/lib.rs
  • crates/recording/src/instant_recording.rs
  • crates/audio/src/latency.rs
  • crates/mediafoundation-ffmpeg/src/h264.rs
  • crates/enc-mediafoundation/src/lib.rs
  • crates/recording/src/lib.rs
  • crates/enc-avfoundation/src/mp4.rs
  • crates/recording/src/sources/screen_capture/macos.rs
  • crates/recording/src/output_pipeline/win.rs
  • crates/recording/src/sources/screen_capture/windows.rs
🧬 Code graph analysis (3)
crates/recording/src/instant_recording.rs (1)
crates/recording/src/studio_recording.rs (1)
  • with_excluded_windows (387-390)
crates/recording/src/lib.rs (1)
apps/desktop/src/utils/tauri.ts (2)
  • LogicalBounds (418-418)
  • WindowId (490-490)
crates/recording/src/output_pipeline/win.rs (6)
crates/enc-ffmpeg/src/video/h264.rs (1)
  • builder (234-236)
crates/recording/src/output_pipeline/core.rs (2)
  • builder (39-46)
  • finish (802-802)
crates/mediafoundation-ffmpeg/src/h264.rs (1)
  • finish (151-163)
crates/enc-ffmpeg/src/mux/mp4.rs (1)
  • finish (111-144)
crates/enc-ffmpeg/src/mux/ogg.rs (1)
  • finish (48-61)
crates/recording/src/output_pipeline/ffmpeg.rs (2)
  • finish (74-93)
  • finish (148-150)
⏰ 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). (3)
  • GitHub Check: Build Desktop (x86_64-pc-windows-msvc, windows-latest)
  • GitHub Check: Build Desktop (aarch64-apple-darwin, macos-latest)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (22)
crates/camera-windows/src/lib.rs (1)

9-9: LGTM! Unused import removed.

This cleanup removes the unused Instant import while retaining Duration, which is correctly used for frame timestamps.

crates/recording/src/sources/screen_capture/macos.rs (1)

10-10: LGTM! Import explicitly declared.

Making the Timestamp import explicit improves code clarity and reduces reliance on the parent module's prelude. The import is correctly positioned and necessary for all the Timestamp usages throughout this file.

crates/audio/src/latency.rs (1)

233-234: LGTM! Proper platform-specific constant.

The #[cfg(target_os = "macos")] attribute correctly gates this constant to macOS-only builds, where it's used in the fallback latency calculation for wireless devices. The 200ms fallback value is appropriate for wireless audio latency.

crates/enc-mediafoundation/src/lib.rs (1)

5-5: ****

All four modules in lib.rs (d3d, media, mft, and video) have public visibility and are treated uniformly. The mft module's public status is consistent with the existing crate architecture, not a unique exposure. The module already contains public items (EncoderDevice struct, enumerate functions), so there is no API expansion concern.

Likely an incorrect or invalid review comment.

crates/recording/src/sources/screen_capture/windows.rs (5)

2-2: LGTM! Import cleanup improves clarity.

The import reorganization removes unused dependencies and introduces the necessary CancellationToken for the new cancellation-aware logging task.

Also applies to: 8-8, 21-21, 23-23


202-203: LGTM! Proper initialization of counter and cancellation token.

The use of Arc<AtomicU32> for the frame counter enables thread-safe counting across closures, and the CancellationToken is correctly initialized for managing the logging task lifecycle.


209-219: LGTM! Frame counter integration is correct.

The atomic counter increment with Relaxed ordering is appropriate for this metrics use case, and the integration doesn't disrupt the existing frame processing logic.


248-260: LGTM! Cancellation-aware logging task is well-structured.

The periodic logging task correctly uses with_cancellation_token_owned for graceful cancellation and in_current_span() to maintain tracing context. This provides valuable observability into frame capture performance.


261-261: LGTM! Drop guard pattern ensures proper task cleanup.

The drop_guard ensures the logging task is cancelled when the capture thread exits. Explicitly dropping it at line 282 after processing the Stop control provides deterministic cleanup timing.

Also applies to: 282-282

crates/recording/src/lib.rs (1)

15-15: LGTM! Import refactoring is correct.

The addition of the explicit LogicalBounds import is appropriate and used at line 65.

crates/recording/src/instant_recording.rs (2)

254-256: LGTM! Field type qualification is consistent.

The field type update aligns with the changes in lib.rs and maintains consistency across the macOS-specific recording API.


286-290: LGTM! Method signature update is consistent.

The with_excluded_windows method signature correctly reflects the fully qualified type, maintaining consistency with the ActorBuilder field and the broader refactoring.

crates/enc-avfoundation/src/mp4.rs (8)

51-60: LGTM! Good error type consolidation.

Renaming to QueueFrameError and adding Construct and NotReadyForMore variants provides better error granularity for both video and audio paths.


213-215: Verify the intentional difference in early-return logic.

The video path returns early only when is_paused (line 213), allowing it to start the writing session (lines 221-225). However, the audio path (lines 282-284) returns early when is_paused || !is_writing. This means:

  • Video frames can initiate the session
  • Audio frames are dropped if the session hasn't started

Confirm this asymmetry is intentional.


277-279: LGTM! Good performance optimization.

Changing the signature to accept &frame::Audio instead of frame::Audio avoids unnecessary copying.


286-288: LGTM! Correct error handling.

Returning QueueFrameError::Failed when audio_input is None is the appropriate behavior.


297-299: LGTM! Past review concern addressed.

Returning Err(QueueFrameError::NotReadyForMore) when the audio input is not ready provides proper backpressure signaling and prevents silent frame drops, aligning with the video path behavior.


310-377: LGTM! Consistent error mapping.

Using QueueFrameError::Construct for all cidre::os::Error results from buffer and format descriptor operations provides consistent error handling throughout the audio path.


402-417: LGTM! Appropriate method signature.

Converting audio_frame_duration to a static method is correct since it doesn't require instance state.


610-626: Good consolidation, but verify the Ok(false) case.

The helper function centralizes error handling effectively. However, when append_sample_buf returns Ok(false) and the writer status is not Failed (line 618), the function returns Ok(()) (line 625). This means the caller receives a success signal even though the frame may not have been appended.

Since readiness is checked before calling this helper (lines 217-219, 297-299), the Ok(false) case likely represents an async operation in progress rather than a "not ready" condition. Please confirm this interpretation is correct and that returning Ok(()) in this scenario won't lead to frame drops.

crates/recording/src/output_pipeline/win.rs (2)

44-44: LGTM: Correct trait association.

The associated Finish type correctly implements the Muxer trait requirement.


3-3: LGTM: Import path updates.

The import paths have been updated to use more specific module paths (aac::AACEncoder and h264::H264Encoder), which aligns with the library reorganization and makes the module structure more explicit.

Also applies to: 114-114

@Brendonovich Brendonovich force-pushed the handle-all-recording-errors branch from ddfd128 to 11668ac Compare October 28, 2025 08:55
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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (7)
crates/recording/src/sources/screen_capture/windows.rs (1)

185-185: Fix indentation: replace tabs with spaces.

Verification confirms tab characters are present in the file. Rust code must use 4-space indentation per rustfmt standards. The grep output shows tabs in multiple locations:

  • Closure body (lines 191–201)
  • Async logging task (lines 204–209, 231–241)
  • Drop guard setup area (lines 230–264)

Run cargo fmt to automatically fix all indentation before merging.

crates/recording/src/sources/audio_mixer.rs (6)

247-256: Silence filling uses 0 bytes for all formats; U8 silence should be 128.

Zeroing bytes yields DC offset for unsigned PCM (e.g., U8). Fill per sample format to avoid audible artifacts and clipping during resample/mix.

Apply a helper and use it at all three sites:

@@
-                        for i in 0..frame.planes() {
-                            frame.data_mut(i).fill(0);
-                        }
+                        fill_silence(&mut frame, source.info.sample_format);
@@
-                                for i in 0..frame.planes() {
-                                    frame.data_mut(i).fill(0);
-                                }
+                                fill_silence(&mut frame, source.info.sample_format);
@@
-                        for i in 0..frame.planes() {
-                            frame.data_mut(i).fill(0);
-                        }
+                        fill_silence(&mut frame, source.info.sample_format);

Add the helper near other free fns:

+fn fill_silence(frame: &mut ffmpeg::frame::Audio, sample_format: cap_media_info::Sample) {
+    // For unsigned PCM, mid‑level (128) is silence; others use 0.
+    let byte = match sample_format {
+        cap_media_info::Sample::U8(_) => 128u8,
+        _ => 0u8,
+    };
+    for i in 0..frame.planes() {
+        frame.data_mut(i).fill(byte);
+    }
+}

Also applies to: 301-312, 367-373


356-382: Start-gap prefill order is reversed due to push_front inside the loop.

Pushing each generated frame to the front produces newest→oldest ordering. Drain will feed frames in reverse chronological order.

Prefill into a Vec in forward time, then push_front in reverse:

-                    let mut remaining = elapsed_since_start;
-                    while remaining > buffer_timeout {
+                    let mut remaining = elapsed_since_start;
+                    let mut to_prepend: Vec<AudioFrame> = Vec::new();
+                    while remaining > buffer_timeout {
                         let chunk_samples = samples_for_timeout(rate, buffer_timeout);
                         let frame_duration = duration_from_samples(chunk_samples, rate);
@@
-                        source.buffer_last = Some((timestamp, frame_duration));
-                        source.buffer.push_front(AudioFrame::new(frame, timestamp));
+                        source.buffer_last = Some((timestamp, frame_duration));
+                        to_prepend.push(AudioFrame::new(frame, timestamp));
@@
-                    }
+                    }
+                    for f in to_prepend.into_iter().rev() {
+                        source.buffer.push_front(f);
+                    }

416-422: Backpressure: treating any try_send error as terminal stops the mixer.

Distinguish Full (transient) from Closed (terminal). On Full, yield and continue next tick.

+use futures::channel::mpsc::TrySendError;
@@
-            if self
-                .output
-                .try_send(AudioFrame::new(filtered, Timestamp::Instant(timestamp)))
-                .is_err()
-            {
-                return Err(());
-            }
+            match self
+                .output
+                .try_send(AudioFrame::new(filtered, Timestamp::Instant(timestamp)))
+            {
+                Ok(()) => {}
+                Err(TrySendError::Full(_)) => {
+                    // Drop frame or yield; here we yield and try again next tick.
+                    debug!("AudioMixer output channel full; yielding");
+                    break;
+                }
+                Err(TrySendError::Closed(_)) => {
+                    info!("AudioMixer output channel closed; stopping");
+                    return Err(());
+                }
+            }

171-203: Timestamp origin mismatch: run() computes ‘start’ before build(); tick() should align with mixer.timestamps.

Small drift can occur if build() advances time before self.timestamps is captured. Use mixer.timestamps for tick origin.

-        let start = Timestamps::now();
-
-        let mut mixer = match self.build(output) {
+        let mut mixer = match self.build(output) {
             Ok(mixer) => mixer,
@@
-        let _ = ready_tx.send(Ok(()));
+        let _ = ready_tx.send(Ok(()));
+        let start = mixer.timestamps;

Optionally, remove the start param from tick() and use self.timestamps internally to avoid future misuse.

Also applies to: 411-413


529-531: Unsafe transmute reads incorrect length; use proper byte count or cast_slice.

byte_count omits sizeof::(), so the &[f32] slice length is wrong, causing UB.

-        let byte_count = frame.samples() * frame.channels() as usize;
-        let samples: &[f32] = unsafe { std::mem::transmute(&frame.data(0)[0..byte_count]) };
+        let bytes = frame.data(0);
+        // Each f32 is 4 bytes; cast with alignment check.
+        let samples: &[f32] = bytemuck::try_cast_slice(bytes)
+            .expect("Audio frame data not f32‑aligned/len not multiple of 4");

Add a dev-dependency and import in the test module:

+[dev-dependencies]
+bytemuck = "1"
-    use futures::{SinkExt, StreamExt};
+    use futures::{SinkExt, StreamExt};
+    use bytemuck;

If avoiding new deps, compute length via from_raw_parts with correct len = bytes.len()/4 and alignment assertions.


68-111: Fix unhandled error cases in filter graph wiring and validate empty sources.

The code currently ignores errors from link() calls (lines 106, 139, 142, 143) despite calling filter_graph.add() and filter_graph.validate() with ? operators elsewhere. Additionally, filter::find() uses .expect() (lines 92, 98, 113, 127, 133), which panics if a filter isn't found, and there's no validation for empty sources before constructing amix with inputs=abuffers.len().

  • Add an early guard: if self.sources.is_empty(), return an appropriate error rather than passing inputs=0 to amix.
  • Propagate link() errors with ? operator (all four calls).
  • Replace .expect() on filter::find() with proper error handling (e.g., .ok_or(...) with an appropriate error variant).

Verify the correct ffmpeg::Error variant to use for filter-not-found and empty-sources cases in your ffmpeg-next fork before finalizing.

♻️ Duplicate comments (1)
crates/enc-ffmpeg/src/mux/mp4.rs (1)

120-132: Fix log messages: use “flush”, and correct the audio path label.

Use terminology consistent with encoder APIs and fix the copy/paste in the audio branch.

Apply this diff:

-        let video_finish = self.video.flush(&mut self.output).inspect_err(|e| {
-            error!("Failed to finish video encoder: {e:#}");
-        });
+        let video_finish = self.video.flush(&mut self.output).inspect_err(|e| {
+            error!("Failed to flush video encoder: {e:#}");
+        });

@@
-                enc.flush(&mut self.output).inspect_err(|e| {
-                    error!("Failed to finish video encoder: {e:#}");
-                })
+                enc.flush(&mut self.output).inspect_err(|e| {
+                    error!("Failed to flush audio encoder: {e:#}");
+                })

Run to find any remaining “Failed to finish … encoder” logs:

#!/bin/bash
rg -nP 'Failed to finish (video|audio) encoder'
🧹 Nitpick comments (10)
crates/recording/src/sources/screen_capture/windows.rs (1)

26-27: Remove commented constants or use them.

Lines 26-27 comment out LOG_INTERVAL, yet Line 233 hardcodes Duration::from_secs(5). Either uncomment and use the constant for maintainability, or remove the commented-out constants entirely.

Apply this diff to use the constant:

-// const WINDOW_DURATION: Duration = Duration::from_secs(3);
-// const LOG_INTERVAL: Duration = Duration::from_secs(5);
+const LOG_INTERVAL: Duration = Duration::from_secs(5);
 const MAX_DROP_RATE_THRESHOLD: f64 = 0.25;

Then at Line 233:

-                    tokio::time::sleep(Duration::from_secs(5)).await;
+                    tokio::time::sleep(LOG_INTERVAL).await;

Alternatively, remove the commented constants if they're no longer needed.

Also applies to: 233-233

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

189-208: Consider logging Windows API failures for debugging.

The Windows API calls now explicitly discard their return values to satisfy Clippy warnings. While this is acceptable for these UI operations (which can legitimately fail in normal circumstances), adding error logging would help with debugging when window focusing doesn't work as expected.

Consider applying this diff to add logging:

                 if GetWindowPlacement(hwnd, &mut wp).is_ok() {
                     // Restore using the previous placement to avoid resizing
                     wp.showCmd = SW_RESTORE.0 as u32;
-                    let _ = SetWindowPlacement(hwnd, &wp);
+                    if let Err(e) = SetWindowPlacement(hwnd, &wp) {
+                        error!("Failed to restore window placement: {e}");
+                    }
                 } else {
                     // Fallback to simple restore if placement fails
-                    let _ = ShowWindow(hwnd, SW_RESTORE);
+                    if ShowWindow(hwnd, SW_RESTORE) == false.into() {
+                        error!("Failed to show window");
+                    }
                 }
             }
 
             // Always try to bring to foreground
-            let _ = SetForegroundWindow(hwnd);
+            if SetForegroundWindow(hwnd) == false.into() {
+                error!("Failed to set window to foreground");
+            }

Can you confirm whether silent failures are acceptable here, or if logging would be beneficial for debugging window focus issues?

crates/enc-avfoundation/src/mp4.rs (1)

646-655: Consider making the polling interval configurable.

The 2ms sleep in the polling loop is hardcoded. While reasonable for most cases, making this configurable could be beneficial for testing or different performance profiles.

+const WRITER_POLL_INTERVAL_MS: u64 = 2;
+
 fn wait_for_writer_finished(writer: &av::AssetWriter) -> Result<(), ()> {
     use av::asset::writer::Status;
     loop {
         match writer.status() {
             Status::Completed | Status::Cancelled => return Ok(()),
             Status::Failed | Status::Unknown => return Err(()),
-            Status::Writing => std::thread::sleep(Duration::from_millis(2)),
+            Status::Writing => std::thread::sleep(Duration::from_millis(WRITER_POLL_INTERVAL_MS)),
         }
     }
 }
crates/recording/src/output_pipeline/ffmpeg.rs (2)

33-36: Remove unused Finish struct.

Dead code; will trigger clippy’s dead_code.

Apply this diff:

-pub struct Finish {
-    pub video_result: Result<(), ffmpeg::Error>,
-    pub audio_result: Result<(), ffmpeg::Error>,
-}

159-160: Flatten Ok wrapping for clarity.

Avoid Ok(self.0.queue_frame(...)?) to reduce cognitive load.

Apply this diff:

-        Ok(self.0.queue_frame(frame.inner, timestamp)?)
+        self.0.queue_frame(frame.inner, timestamp)?;
+        Ok(())
crates/recording/src/sources/audio_mixer.rs (5)

388-396: Unify timestamp base inside tick().

tick(start, now) mixes ‘start’ with self.timestamps. Prefer using self.timestamps consistently; or assert start == self.timestamps to catch misuse.

-    fn tick(&mut self, start: Timestamps, now: Timestamp) -> Result<(), ()> {
+    fn tick(&mut self, start: Timestamps, now: Timestamp) -> Result<(), ()> {
@@
-            let timestamp = start.instant() + start_timestamp.duration_since(start) + elapsed;
+            let timestamp = self.timestamps.instant()
+                + start_timestamp.duration_since(self.timestamps)
+                + elapsed;

Optionally remove the start parameter entirely in a follow-up.

Also applies to: 411-413


522-526: Use mixer.timestamps in tests for consistency with production path.

You already do this elsewhere; mirror it here to avoid timestamp origin drift.

-        let _ = mixer.tick(
-            start,
+        let _ = mixer.tick(
+            mixer.timestamps,
             Timestamp::Instant(start.instant() + Duration::from_secs_f64(4.0 / SAMPLE_RATE as f64)),
         );

482-486: Minor: tests import only what’s needed.

You can drop SinkExt in tests that never call send().await, but keep it where used. Keeps clippy happy.


56-66: Builder silently accepts zero buffer_size/sample_rate.

buffer_timeout_for falls back to default, which is fine. Consider logging at debug level when defaults kick in to aid troubleshooting device quirks.


121-130: Nit: aformat args can be computed once as constants.

Given INFO is const, you can make aformat args a &'static str via lazy const to avoid formatting per build.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f4ba429 and 5935df1.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (42)
  • .github/workflows/ci.yml (2 hunks)
  • .github/workflows/publish.yml (1 hunks)
  • Cargo.toml (1 hunks)
  • apps/desktop/src-tauri/src/target_select_overlay.rs (1 hunks)
  • crates/audio/src/latency.rs (2 hunks)
  • crates/camera-directshow/src/lib.rs (1 hunks)
  • crates/camera-windows/src/lib.rs (1 hunks)
  • crates/enc-avfoundation/src/mp4.rs (12 hunks)
  • crates/enc-ffmpeg/src/audio/aac.rs (2 hunks)
  • crates/enc-ffmpeg/src/audio/audio_encoder.rs (1 hunks)
  • crates/enc-ffmpeg/src/audio/base.rs (1 hunks)
  • crates/enc-ffmpeg/src/audio/mod.rs (1 hunks)
  • crates/enc-ffmpeg/src/audio/opus.rs (3 hunks)
  • crates/enc-ffmpeg/src/lib.rs (1 hunks)
  • crates/enc-ffmpeg/src/mux/mod.rs (1 hunks)
  • crates/enc-ffmpeg/src/mux/mp4.rs (4 hunks)
  • crates/enc-ffmpeg/src/mux/ogg.rs (2 hunks)
  • crates/enc-ffmpeg/src/video/h264.rs (1 hunks)
  • crates/enc-ffmpeg/src/video/mod.rs (0 hunks)
  • crates/enc-mediafoundation/src/lib.rs (1 hunks)
  • crates/enc-mediafoundation/src/unsafe_send.rs (0 hunks)
  • crates/enc-mediafoundation/src/video/h264.rs (0 hunks)
  • crates/export/src/mp4.rs (2 hunks)
  • crates/mediafoundation-ffmpeg/src/h264.rs (1 hunks)
  • crates/recording/Cargo.toml (1 hunks)
  • crates/recording/examples/camera.rs (1 hunks)
  • crates/recording/src/instant_recording.rs (2 hunks)
  • crates/recording/src/lib.rs (2 hunks)
  • crates/recording/src/output_pipeline/core.rs (4 hunks)
  • crates/recording/src/output_pipeline/ffmpeg.rs (6 hunks)
  • crates/recording/src/output_pipeline/macos.rs (3 hunks)
  • crates/recording/src/output_pipeline/win.rs (3 hunks)
  • crates/recording/src/sources/audio_mixer.rs (3 hunks)
  • crates/recording/src/sources/screen_capture/macos.rs (1 hunks)
  • crates/recording/src/sources/screen_capture/mod.rs (0 hunks)
  • crates/recording/src/sources/screen_capture/windows.rs (1 hunks)
  • crates/recording/src/studio_recording.rs (2 hunks)
  • crates/rendering/src/zoom.rs (1 hunks)
  • crates/scap-ffmpeg/examples/cli.rs (1 hunks)
  • crates/scap-screencapturekit/Cargo.toml (1 hunks)
  • crates/scap-screencapturekit/examples/cli.rs (2 hunks)
  • crates/timestamp/src/macos.rs (0 hunks)
💤 Files with no reviewable changes (5)
  • crates/enc-ffmpeg/src/video/mod.rs
  • crates/timestamp/src/macos.rs
  • crates/enc-mediafoundation/src/video/h264.rs
  • crates/recording/src/sources/screen_capture/mod.rs
  • crates/enc-mediafoundation/src/unsafe_send.rs
🚧 Files skipped from review as they are similar to previous changes (15)
  • crates/camera-directshow/src/lib.rs
  • .github/workflows/publish.yml
  • crates/scap-screencapturekit/Cargo.toml
  • crates/camera-windows/src/lib.rs
  • crates/enc-ffmpeg/src/mux/mod.rs
  • crates/mediafoundation-ffmpeg/src/h264.rs
  • crates/recording/examples/camera.rs
  • crates/scap-ffmpeg/examples/cli.rs
  • crates/enc-ffmpeg/src/lib.rs
  • crates/recording/src/sources/screen_capture/macos.rs
  • crates/enc-ffmpeg/src/audio/aac.rs
  • crates/enc-ffmpeg/src/video/h264.rs
  • crates/export/src/mp4.rs
  • crates/enc-mediafoundation/src/lib.rs
  • .github/workflows/ci.yml
🧰 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:

  • apps/desktop/src-tauri/src/target_select_overlay.rs
  • crates/recording/src/lib.rs
  • crates/enc-ffmpeg/src/audio/audio_encoder.rs
  • crates/recording/src/studio_recording.rs
  • crates/recording/src/output_pipeline/win.rs
  • crates/recording/src/sources/audio_mixer.rs
  • crates/enc-ffmpeg/src/mux/ogg.rs
  • crates/recording/src/instant_recording.rs
  • crates/scap-screencapturekit/examples/cli.rs
  • crates/recording/src/output_pipeline/macos.rs
  • crates/recording/src/output_pipeline/ffmpeg.rs
  • crates/recording/src/output_pipeline/core.rs
  • crates/recording/src/sources/screen_capture/windows.rs
  • crates/audio/src/latency.rs
  • crates/rendering/src/zoom.rs
  • crates/enc-avfoundation/src/mp4.rs
  • crates/enc-ffmpeg/src/mux/mp4.rs
  • crates/enc-ffmpeg/src/audio/mod.rs
  • crates/enc-ffmpeg/src/audio/opus.rs
  • crates/enc-ffmpeg/src/audio/base.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/lib.rs
  • crates/enc-ffmpeg/src/audio/audio_encoder.rs
  • crates/recording/src/studio_recording.rs
  • crates/recording/src/output_pipeline/win.rs
  • crates/recording/src/sources/audio_mixer.rs
  • crates/enc-ffmpeg/src/mux/ogg.rs
  • crates/recording/src/instant_recording.rs
  • crates/recording/src/output_pipeline/macos.rs
  • crates/recording/src/output_pipeline/ffmpeg.rs
  • crates/recording/src/output_pipeline/core.rs
  • crates/recording/src/sources/screen_capture/windows.rs
  • crates/audio/src/latency.rs
  • crates/rendering/src/zoom.rs
  • crates/enc-avfoundation/src/mp4.rs
  • crates/enc-ffmpeg/src/mux/mp4.rs
  • crates/enc-ffmpeg/src/audio/mod.rs
  • crates/enc-ffmpeg/src/audio/opus.rs
  • crates/enc-ffmpeg/src/audio/base.rs
🧠 Learnings (3)
📚 Learning: 2025-10-28T08:07:55.375Z
Learnt from: Brendonovich
PR: CapSoftware/Cap#1305
File: crates/scap-screencapturekit/examples/cli.rs:32-36
Timestamp: 2025-10-28T08:07:55.375Z
Learning: Example files (in paths containing `/examples/`) don't need to follow the same strict patterns as production code. Pragmatic and simpler patterns are acceptable in examples even if they wouldn't be ideal for production use.

Applied to files:

  • crates/scap-screencapturekit/examples/cli.rs
📚 Learning: 2025-10-28T08:39:42.216Z
Learnt from: Brendonovich
PR: CapSoftware/Cap#1305
File: crates/recording/src/output_pipeline/macos.rs:80-90
Timestamp: 2025-10-28T08:39:42.216Z
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/macos.rs
  • crates/recording/src/output_pipeline/ffmpeg.rs
  • crates/enc-avfoundation/src/mp4.rs
📚 Learning: 2025-10-17T05:58:22.586Z
Learnt from: Brendonovich
PR: CapSoftware/Cap#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/macos.rs
🧬 Code graph analysis (15)
crates/recording/src/lib.rs (1)
apps/desktop/src/utils/tauri.ts (2)
  • LogicalBounds (418-418)
  • WindowId (490-490)
crates/enc-ffmpeg/src/audio/audio_encoder.rs (4)
crates/enc-ffmpeg/src/audio/aac.rs (2)
  • flush (107-109)
  • flush (117-119)
crates/enc-ffmpeg/src/audio/base.rs (1)
  • flush (39-45)
crates/enc-ffmpeg/src/audio/opus.rs (2)
  • flush (101-103)
  • flush (119-121)
crates/enc-ffmpeg/src/audio/buffered_resampler.rs (2)
  • flush (264-271)
  • output (54-56)
crates/recording/src/studio_recording.rs (1)
crates/recording/src/instant_recording.rs (1)
  • with_excluded_windows (287-290)
crates/recording/src/output_pipeline/win.rs (5)
crates/recording/src/output_pipeline/core.rs (1)
  • finish (807-807)
crates/enc-ffmpeg/src/mux/ogg.rs (1)
  • finish (52-65)
crates/recording/src/output_pipeline/ffmpeg.rs (2)
  • finish (73-95)
  • finish (149-154)
crates/recording/src/output_pipeline/macos.rs (1)
  • finish (54-61)
crates/enc-ffmpeg/src/mux/mp4.rs (1)
  • finish (111-144)
crates/recording/src/sources/audio_mixer.rs (2)
crates/recording/src/output_pipeline/core.rs (7)
  • new (625-627)
  • new (668-670)
  • new (701-703)
  • new (745-762)
  • start (728-730)
  • start (778-780)
  • timestamp (788-788)
crates/timestamp/src/lib.rs (2)
  • now (103-112)
  • duration_since (24-33)
crates/enc-ffmpeg/src/mux/ogg.rs (2)
crates/enc-ffmpeg/src/audio/opus.rs (1)
  • queue_frame (92-99)
crates/recording/src/output_pipeline/ffmpeg.rs (3)
  • timestamp (22-24)
  • finish (73-95)
  • finish (149-154)
crates/recording/src/instant_recording.rs (1)
crates/recording/src/studio_recording.rs (1)
  • with_excluded_windows (386-389)
crates/scap-screencapturekit/examples/cli.rs (1)
crates/scap-screencapturekit/src/capture.rs (1)
  • builder (78-87)
crates/recording/src/output_pipeline/macos.rs (3)
crates/enc-ffmpeg/src/mux/ogg.rs (1)
  • finish (52-65)
crates/recording/src/output_pipeline/core.rs (2)
  • finish (807-807)
  • timestamp (788-788)
crates/recording/src/output_pipeline/ffmpeg.rs (3)
  • finish (73-95)
  • finish (149-154)
  • timestamp (22-24)
crates/recording/src/output_pipeline/ffmpeg.rs (6)
crates/enc-ffmpeg/src/mux/ogg.rs (1)
  • finish (52-65)
crates/recording/src/output_pipeline/core.rs (2)
  • finish (807-807)
  • timestamp (788-788)
crates/recording/src/output_pipeline/macos.rs (1)
  • finish (54-61)
crates/recording/src/output_pipeline/win.rs (1)
  • finish (275-289)
crates/enc-avfoundation/src/mp4.rs (1)
  • finish (450-496)
crates/enc-ffmpeg/src/mux/mp4.rs (1)
  • finish (111-144)
crates/recording/src/output_pipeline/core.rs (4)
crates/enc-ffmpeg/src/mux/ogg.rs (1)
  • finish (52-65)
crates/recording/src/output_pipeline/ffmpeg.rs (3)
  • finish (73-95)
  • finish (149-154)
  • timestamp (22-24)
crates/recording/src/output_pipeline/macos.rs (1)
  • finish (54-61)
crates/recording/src/output_pipeline/win.rs (1)
  • finish (275-289)
crates/enc-avfoundation/src/mp4.rs (4)
crates/recording/src/output_pipeline/core.rs (2)
  • timestamp (788-788)
  • finish (807-807)
crates/recording/src/output_pipeline/ffmpeg.rs (3)
  • timestamp (22-24)
  • finish (73-95)
  • finish (149-154)
crates/recording/src/output_pipeline/macos.rs (1)
  • finish (54-61)
crates/recording/src/output_pipeline/win.rs (1)
  • finish (275-289)
crates/enc-ffmpeg/src/mux/mp4.rs (2)
crates/enc-ffmpeg/src/mux/ogg.rs (1)
  • finish (52-65)
crates/recording/src/output_pipeline/ffmpeg.rs (2)
  • finish (73-95)
  • finish (149-154)
crates/enc-ffmpeg/src/audio/opus.rs (4)
crates/enc-ffmpeg/src/audio/aac.rs (4)
  • flush (107-109)
  • flush (117-119)
  • send_frame (98-105)
  • send_frame (113-115)
crates/enc-ffmpeg/src/audio/audio_encoder.rs (2)
  • flush (12-12)
  • send_frame (11-11)
crates/enc-ffmpeg/src/audio/base.rs (2)
  • flush (39-45)
  • send_frame (21-37)
crates/enc-ffmpeg/src/audio/buffered_resampler.rs (2)
  • flush (264-271)
  • output (54-56)
crates/enc-ffmpeg/src/audio/base.rs (4)
crates/enc-ffmpeg/src/audio/aac.rs (2)
  • flush (107-109)
  • flush (117-119)
crates/enc-ffmpeg/src/audio/audio_encoder.rs (1)
  • flush (12-12)
crates/enc-ffmpeg/src/audio/opus.rs (2)
  • flush (101-103)
  • flush (119-121)
crates/enc-ffmpeg/src/audio/buffered_resampler.rs (2)
  • flush (264-271)
  • output (54-56)
⏰ 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). (3)
  • GitHub Check: Build Desktop (aarch64-apple-darwin, macos-latest)
  • GitHub Check: Build Desktop (x86_64-pc-windows-msvc, windows-latest)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (41)
crates/rendering/src/zoom.rs (1)

243-243: LGTM! Explicit lifetime improves clarity.

The explicit lifetime annotation makes the borrow relationship between the input slice and returned cursor more obvious, and likely addresses a clippy warning mentioned in the PR objectives.

Cargo.toml (1)

67-67: LGTM! Enforcing must_use handling improves code quality.

The addition of unused_must_use = "deny" at the workspace level is an excellent practice that aligns with the PR's focus on improving error handling. This will ensure that Result types and other #[must_use] values are explicitly handled across the codebase.

crates/audio/src/latency.rs (2)

233-234: LGTM! macOS-specific wireless latency constant is appropriate.

The addition of WIRELESS_FALLBACK_LATENCY_SECS for macOS provides a more accurate fallback for wireless audio devices (200ms vs 30ms for wired). This aligns with the documented wireless audio delays mentioned in the module header (120ms+ for Bluetooth).


619-619: LGTM! Test lint suppression is appropriate.

Suppressing clippy::unchecked_duration_subtraction in tests is reasonable, as test code controls the timing values and ensures they don't underflow. This aligns with the workspace-level lint enforcement being added in this PR.

crates/recording/src/instant_recording.rs (2)

255-255: LGTM! Consistent type qualification for WindowId.

The change to Vec<scap_targets::WindowId> provides explicit type qualification and aligns with the broader refactoring to unify WindowId usage across macOS-specific code paths.


287-290: LGTM! Method signature updated consistently.

The with_excluded_windows method signature correctly uses the fully qualified Vec<scap_targets::WindowId> type, maintaining consistency with the struct field and other modules.

crates/recording/src/lib.rs (2)

15-15: LGTM! Import simplified to use qualified WindowId.

Removing WindowId from the import statement and using the fully qualified scap_targets::WindowId path throughout the codebase improves clarity and consistency.


51-51: LGTM! Type qualification is consistent with other modules.

The change to Vec<scap_targets::WindowId> aligns with the refactoring across instant_recording.rs and studio_recording.rs. Note that a previous review comment indicated that screen_capture/mod.rs may also need similar updates, though that file is not included in this review batch.

crates/recording/src/studio_recording.rs (2)

348-348: LGTM! Struct field type updated consistently.

The excluded_windows field now uses the fully qualified Vec<scap_targets::WindowId> type, consistent with the changes in instant_recording.rs and lib.rs.


386-389: LGTM! Method signature updated to match struct field.

The with_excluded_windows method signature correctly uses Vec<scap_targets::WindowId>, maintaining consistency across the recording module's public API.

crates/recording/Cargo.toml (1)

51-51: Retry dependency is properly used in the codebase.

Verification confirms the retry crate is actively used in crates/recording/src/output_pipeline/macos.rs with proper imports and multiple usage sites for error handling. The dependency aligns with the PR objectives for improved AVFoundation error handling.

crates/scap-screencapturekit/examples/cli.rs (1)

8-53: Example code patterns are acceptable per learning.

The changes update this example to use async patterns with #[tokio::main] and introduce cidre::sc usage. While there are mixing concerns with block_on/.await, display shadowing, and std::thread::sleep in async context, these were previously raised and you indicated that pragmatic patterns are acceptable in example files.

Based on learnings.

crates/recording/src/sources/screen_capture/windows.rs (1)

2-2: LGTM: Import cleanup aligns with new cancellation pattern.

The import changes appropriately remove unused symbols and add CancellationToken support for the new task cancellation scaffolding.

Also applies to: 8-8, 21-21, 23-23

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

365-371: LGTM: Nested Result handling correctly distinguishes muxer vs encoder failures.

The code correctly extracts the inner Result from muxer finish and logs encoder flush failures as warnings, allowing the pipeline to complete successfully even when encoder flushing encounters errors. This aligns with the PR objective of ensuring MP4 files remain valid even if encoder flushing fails.


706-712: LGTM: Idiomatic Deref implementation.

The Deref implementation enables ergonomic access to the underlying ffmpeg::frame::Audio, allowing callers to use AudioFrame as if it were an Audio directly.


807-807: LGTM: Nested Result signature enables proper error layering.

The updated signature anyhow::Result<anyhow::Result<()>> allows implementations to distinguish between muxer-level failures (outer Result) and encoder stream failures (inner Result), supporting the PR's goal of writing valid MP4 trailers even when encoder flushing fails.

crates/enc-avfoundation/src/mp4.rs (4)

218-283: LGTM: Video frame queuing with proper backpressure.

The early NotReadyForMore check properly signals backpressure to callers before attempting to append frames, enabling the retry logic in the muxer layer to function correctly.


287-395: LGTM: Audio queuing now consistent with video path.

The audio path now returns Err(QueueFrameError::NotReadyForMore) when the encoder is busy, resolving the inconsistency flagged in the previous review where audio silently dropped frames while video signaled backpressure.


450-496: LGTM: Finish implementation with proper error handling.

The implementation correctly handles edge cases and propagates errors appropriately. Best-effort final frame queuing with error logging ensures video extension attempts don't block successful completion.


499-503: LGTM: Drop safely ignores finish result.

Ignoring the finish result in Drop is correct, as destructors must not panic and any errors will have been logged by the finish method itself.

crates/enc-ffmpeg/src/audio/audio_encoder.rs (1)

12-12: LGTM: Method rename aligns with encoder terminology.

Renaming finish to flush correctly reflects that encoders flush buffered frames, while muxers finish writing. This aligns with the PR objective of distinguishing between encoder and muxer concepts.

crates/enc-ffmpeg/src/audio/base.rs (1)

39-45: LGTM: Flush implementation preserves correct EOF handling.

The renamed flush method correctly processes remaining buffered frames and sends EOF to the encoder, ensuring clean stream termination.

crates/recording/src/output_pipeline/win.rs (2)

3-3: LGTM: Import paths updated for new module structure.

The import path change to cap_enc_ffmpeg::aac::AACEncoder and to cap_enc_ffmpeg::h264::H264Encoder correctly reflect the module reorganization where audio/video encoder submodules are now explicitly public rather than re-exported.

Also applies to: 109-109


275-289: LGTM: Windows muxer finish correctly implements nested Result pattern.

The implementation flushes the audio encoder, writes the trailer, then returns the encoder result as the inner Result. This ensures the trailer is written even if encoder flushing fails, producing a valid (though potentially incorrect) MP4 file as intended by the PR objectives.

crates/enc-ffmpeg/src/audio/mod.rs (1)

4-9: LGTM: Explicit public submodules improve API clarity.

Making buffered_resampler, aac, and opus explicitly public (while keeping base private) provides better API encapsulation and allows consumers to import specific audio encoder types directly rather than through blanket re-exports.

crates/enc-ffmpeg/src/audio/opus.rs (3)

11-13: LGTM: Import paths updated for explicit submodule structure.

The import paths now explicitly reference audio_encoder, base, and buffered_resampler from crate::audio, consistent with the module visibility changes in audio/mod.rs.


101-103: LGTM: Flush method correctly delegates to base.

The renamed method maintains the same delegation pattern to base.flush, ensuring consistent frame flushing behavior across all audio encoders.


114-122: LGTM: AudioEncoder trait implementation with expected delegation.

The trait implementation provides send_frame and flush methods that correctly delegate to the encoder's internal methods. The Duration::MAX timestamp in send_frame is likely handled appropriately by the base encoder for timestamp-agnostic encoding scenarios.

crates/enc-ffmpeg/src/mux/ogg.rs (4)

4-4: LGTM: updated Opus import path.

Matches the new crate::audio::opus layout.


12-18: LGTM: concise FinishError surface.

Good idempotency variant and trailer error mapping.


52-65: LGTM: finish is idempotent; trailer errors propagate.

Flush result is preserved even if writing the trailer succeeds/fails appropriately.


70-71: Drop behavior is fine.

Ignoring finish result in Drop avoids panics on shutdown.

crates/enc-ffmpeg/src/mux/mp4.rs (5)

31-37: LGTM: FinishError mirrors ogg and propagates trailer failures.


39-43: LGTM: FinishResult captures per-stream flush outcomes.


111-114: LGTM: finish is idempotent.


135-139: LGTM: trailer write errors are returned.


157-157: LGTM: swallow finish in Drop.

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

73-95: LGTM: nested Result finish semantics with trailer propagation.

Matches muxer policy: write trailer even if flush fails; propagate trailer failure.


107-107: LGTM: propagate video queue_frame errors.


117-117: LGTM: propagate audio send_frame errors.


149-154: LGTM: delegate Ogg finish with proper error mapping.

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

♻️ Duplicate comments (1)
crates/export/src/mp4.rs (1)

114-123: Handle per-stream finish results without early return; log warnings for partial failures.

The current implementation returns an error on the first failed stream (video or audio), which doesn't align with the PR objective: "Muxer 'finish' now writes the header even if encoder flushing fails, ensuring resulting MP4 files are valid (though possibly incorrect)."

According to the PR description and the past review comment, the muxer should complete the finish process and log warnings for individual stream failures rather than failing early. The nested Result<FinishResult, FinishError> structure supports this - the outer error represents unrecoverable muxer failures (like trailer write failures), while the inner FinishResult contains per-stream results that should be logged but not propagate as fatal errors.

Apply this diff to log per-stream errors without failing the export:

         let res = encoder
             .finish()
             .map_err(|e| format!("Failed to finish encoding: {e}"))?;

-        if let Err(e) = res.video_finish {
-            return Err(format!("Video encoding failed: {e}"));
-        }
-        if let Err(e) = res.audio_finish {
-            return Err(format!("Audio encoding failed: {e}"));
-        }
+        if let Err(e) = res.video_finish {
+            warn!("Video flush error during finish (file is still finalized): {e}");
+        }
+        if let Err(e) = res.audio_finish {
+            warn!("Audio flush error during finish (file is still finalized): {e}");
+        }
🧹 Nitpick comments (3)
crates/enc-ffmpeg/src/mux/mp4.rs (1)

4-4: Consider explicit tracing imports for clarity.

Using glob imports (use tracing::*;) can reduce code clarity by obscuring which items are imported. While this is a common pattern for tracing macros, consider importing specific items explicitly.

Apply this diff if you prefer explicit imports:

-use tracing::*;
+use tracing::{error, info, trace};
crates/recording/src/output_pipeline/ffmpeg.rs (1)

6-6: Consider explicit imports for h264 and ogg modules.

Using glob imports (h264::*, ogg::*) can reduce clarity by obscuring which items are imported. While this works, consider importing specific items explicitly.

For example:

use cap_enc_ffmpeg::{
    aac::AACEncoder, 
    h264::{H264Encoder, QueueFrameError}, 
    ogg::OggFile, 
    opus::OpusEncoder
};
crates/enc-avfoundation/src/mp4.rs (1)

5-5: Consider explicit tracing imports for clarity.

Using glob imports (use tracing::*;) can reduce code clarity by obscuring which items are imported. While this is a common pattern for tracing macros, consider importing specific items explicitly.

Apply this diff if you prefer explicit imports:

-use tracing::*;
+use tracing::{debug, error, info, trace, warn};
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5935df1 and ef54d4a.

📒 Files selected for processing (4)
  • crates/enc-avfoundation/src/mp4.rs (12 hunks)
  • crates/enc-ffmpeg/src/mux/mp4.rs (4 hunks)
  • crates/export/src/mp4.rs (2 hunks)
  • crates/recording/src/output_pipeline/ffmpeg.rs (5 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/export/src/mp4.rs
  • crates/enc-ffmpeg/src/mux/mp4.rs
  • crates/enc-avfoundation/src/mp4.rs
  • 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/export/src/mp4.rs
  • crates/enc-ffmpeg/src/mux/mp4.rs
  • crates/enc-avfoundation/src/mp4.rs
  • crates/recording/src/output_pipeline/ffmpeg.rs
🧠 Learnings (1)
📚 Learning: 2025-10-28T08:39:42.216Z
Learnt from: Brendonovich
PR: CapSoftware/Cap#1305
File: crates/recording/src/output_pipeline/macos.rs:80-90
Timestamp: 2025-10-28T08:39:42.216Z
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/enc-avfoundation/src/mp4.rs
  • crates/recording/src/output_pipeline/ffmpeg.rs
🧬 Code graph analysis (3)
crates/enc-ffmpeg/src/mux/mp4.rs (3)
crates/recording/src/output_pipeline/ffmpeg.rs (2)
  • finish (68-90)
  • finish (144-149)
crates/enc-ffmpeg/src/mux/ogg.rs (1)
  • finish (52-65)
crates/mediafoundation-ffmpeg/src/h264.rs (1)
  • finish (151-163)
crates/enc-avfoundation/src/mp4.rs (2)
crates/recording/src/output_pipeline/core.rs (2)
  • timestamp (788-788)
  • finish (807-807)
crates/recording/src/output_pipeline/macos.rs (1)
  • finish (54-61)
crates/recording/src/output_pipeline/ffmpeg.rs (6)
crates/enc-ffmpeg/src/mux/mp4.rs (1)
  • finish (111-144)
crates/enc-ffmpeg/src/mux/ogg.rs (1)
  • finish (52-65)
crates/recording/src/output_pipeline/macos.rs (2)
  • finish (54-61)
  • send_audio_frame (94-107)
crates/recording/src/output_pipeline/win.rs (2)
  • finish (275-289)
  • send_audio_frame (305-313)
crates/recording/src/output_pipeline/core.rs (3)
  • finish (807-807)
  • timestamp (788-788)
  • send_audio_frame (811-811)
crates/mediafoundation-ffmpeg/src/h264.rs (1)
  • finish (151-163)
⏰ 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). (4)
  • GitHub Check: Clippy
  • GitHub Check: Build Desktop (aarch64-apple-darwin, macos-latest)
  • GitHub Check: Build Desktop (x86_64-pc-windows-msvc, windows-latest)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (17)
crates/export/src/mp4.rs (1)

3-3: LGTM! Import paths correctly reflect the new module structure.

The updated imports correctly reference the refactored module structure where encoders are now under their specific submodules (aac::AACEncoder, h264::H264Encoder, mp4::*).

crates/enc-ffmpeg/src/mux/mp4.rs (3)

9-9: LGTM! Import paths correctly updated.

The import path correctly reflects the refactored module structure where H264Encoder is now under video::h264.


31-42: LGTM! Error types well-designed for nested result handling.

The new FinishError and FinishResult types properly distinguish between unrecoverable muxer errors (outer Result) and per-stream encoder flush results (inner fields), aligning with the PR's goal to allow partial failures while still completing the muxer finish.


111-144: LGTM! Finish method correctly implements nested error handling.

The finish method properly:

  • Guards against double-finish with FinishError::AlreadyFinished
  • Flushes both video and audio encoders, capturing errors without early returns
  • Logs encoder flush failures via inspect_err
  • Propagates trailer write failures as unrecoverable errors via FinishError::WriteTrailerFailed
  • Returns nested results allowing callers to handle per-stream failures appropriately

This implementation aligns with the PR objective that the muxer finishes even if encoder flushing fails, ensuring valid MP4 files.

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

68-90: LGTM! Finish method correctly implements nested error handling.

The finish method properly:

  • Flushes both video and audio encoders without early returns
  • Writes the trailer (outer Result propagates unrecoverable errors)
  • Returns nested Ok(Ok(())) when all encoders succeed
  • Returns nested Ok(Err(...)) when encoder flush fails, preserving details for both streams

This implementation aligns with the trait signature in output_pipeline/core.rs and the PR objective to complete muxer finish even when encoder flushing fails.


96-106: LGTM! Error propagation correctly implemented.

The video frame queuing properly propagates errors using the ? operator, aligning with the updated queue_frame signature that now returns Result.


109-117: LGTM! Error propagation correctly implemented.

The audio frame sending properly propagates errors using the ? operator, aligning with the updated send_frame signature that now returns Result.


144-149: LGTM! OggMuxer finish correctly delegates and converts errors.

The finish method properly:

  • Delegates to the underlying OggFile::finish()
  • Converts both outer FinishError and inner ffmpeg::Error to anyhow::Error using Into
  • Maintains the nested Result structure required by the Muxer trait

This implementation is consistent with the Mp4Muxer finish pattern.


152-156: LGTM! Error propagation correctly implemented.

The audio frame queuing properly propagates errors from queue_frame using the ? operator and wraps the result appropriately.

crates/enc-avfoundation/src/mp4.rs (8)

51-70: LGTM! Error types well-defined with distinct messages.

The error enums properly define failure modes:

  • QueueFrameError covers append failures, construction errors, and backpressure
  • FinishError has distinct error messages for each variant (NotWriting, NoFrames, Failed)

The past review concern about duplicate error messages has been addressed.


218-229: LGTM! Early return logic correctly handles paused state and backpressure.

The early checks properly:

  • Return early if paused without queueing
  • Check encoder readiness before processing, returning NotReadyForMore for backpressure

This aligns with the retry logic in crates/recording/src/output_pipeline/macos.rs that handles QueueFrameError::NotReadyForMore.

Based on learnings


277-277: LGTM! Centralized append logic via helper function.

The call to the new append_sample_buf helper properly delegates frame appending logic and propagates errors consistently.


287-309: LGTM! Audio frame queuing correctly aligned with video path.

The updated method properly:

  • Takes &frame::Audio by reference for efficiency
  • Returns Result<(), QueueFrameError> for proper error propagation
  • Checks encoder readiness and returns NotReadyForMore for backpressure, consistent with the video path

The past review concern about inconsistent error handling between audio and video paths has been addressed.


311-395: LGTM! Consistent error mapping for construction operations.

All construction operations (BlockBuf::with_mem_block, as_mut_slice, AudioFormatDesc::with_asbd, SampleBuf::create) properly map errors to QueueFrameError::Construct, providing clear error categorization and consistent error handling.


450-496: LGTM! Finish method correctly implements AVFoundation finalization.

The finish method properly:

  • Guards against invalid states (NotWriting, NoFrames)
  • Extends the video to the provided timestamp when appropriate
  • Finalizes the asset writer by ending the session and marking inputs as finished
  • Waits for the writer to complete using the wait_for_writer_finished helper
  • Maps completion failures to FinishError::Failed

This implementation aligns with AVFoundation's asynchronous writing model.


499-503: LGTM! Drop implementation properly finalizes resources.

The Drop implementation correctly calls finish(None) to ensure resources are cleaned up. Ignoring the result with let _ = is appropriate in Drop since errors cannot be propagated, preventing potential panics.


625-655: LGTM! Helper functions properly encapsulate error handling and polling logic.

The helper functions are well-designed:

append_sample_buf (lines 625-644):

  • Centralizes frame appending with consistent error handling
  • Maps append_sample_buf outcomes to appropriate QueueFrameError variants
  • Checks writer status to distinguish between transient (NotReadyForMore) and fatal (Failed) errors
  • Retains exception objects for error reporting

wait_for_writer_finished (lines 646-655):

  • Polls the asset writer status until completion
  • Returns success for Completed or Cancelled states
  • Returns error for Failed or Unknown states
  • Uses appropriate sleep interval (2ms) while Writing

@Brendonovich Brendonovich merged commit 1996cdf into main Oct 28, 2025
15 checks passed
@Brendonovich Brendonovich deleted the handle-all-recording-errors branch October 28, 2025 14:29
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