Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
354bc48
hoooly
Brendonovich Sep 28, 2025
db81c51
windows
Brendonovich Sep 28, 2025
86b5659
fixes
Brendonovich Sep 28, 2025
1425352
bruh
Brendonovich Sep 28, 2025
5f6988b
windows works
Brendonovich Sep 28, 2025
90d3e4c
studio mode actor
Brendonovich Sep 29, 2025
7567bd9
system audio mostly
Brendonovich Sep 29, 2025
2238371
clippy
Brendonovich Sep 29, 2025
e392535
fix capture
Brendonovich Sep 29, 2025
2d441fa
hook up error handling for tasks and done_rx
Brendonovich Sep 30, 2025
9562141
desktop instant recording
Brendonovich Sep 30, 2025
114e513
windows system audio
Brendonovich Oct 1, 2025
8af9e4b
more windows
Brendonovich Oct 1, 2025
1f7e969
use flume for video tx
Brendonovich Oct 2, 2025
9fbb70f
native encoder working
Brendonovich Oct 2, 2025
99da70d
windows working properly
Brendonovich Oct 2, 2025
adc450b
formatting
Brendonovich Oct 2, 2025
f7afeeb
Merge branch 'main' into overhaul-recoring-pipeline
richiemcilroy Oct 2, 2025
b0a1a2f
Implement completion future for studio recording
richiemcilroy Oct 2, 2025
3c49d38
Merge branch 'main' into overhaul-recoring-pipeline
richiemcilroy Oct 2, 2025
ab5703a
cleanup
Brendonovich Oct 2, 2025
a8e0f44
fix
Brendonovich Oct 2, 2025
c066036
fix microphone capture on Windows
richiemcilroy Oct 2, 2025
d4957d1
Merge branch 'overhaul-recoring-pipeline' of https://github.com/CapSo…
richiemcilroy Oct 2, 2025
f01a998
extract ffmpeg encoder logic into common base
Brendonovich Oct 3, 2025
026921a
audio works
Brendonovich Oct 3, 2025
35ce1cd
cargo fix
Brendonovich Oct 3, 2025
aa3915e
Merge branch 'main' into overhaul-recording-pipeline
Brendonovich Oct 3, 2025
0215f89
start moving windows capturer to dedicated thread
Brendonovich Oct 3, 2025
4e2c2d2
fix windows muxer
Brendonovich Oct 3, 2025
383a8d3
some more logging
Brendonovich Oct 3, 2025
d75176c
fix recording-cli
Brendonovich Oct 3, 2025
54d05ad
clippy
Brendonovich Oct 3, 2025
ec2bbbe
fix log
Brendonovich Oct 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion apps/cli/src/record.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ impl RecordStart {
.await
.unwrap();

actor.0.stop().await.unwrap();
actor.stop().await.unwrap();

Ok(())
}
Expand Down
18 changes: 9 additions & 9 deletions apps/desktop/src-tauri/src/camera.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use anyhow::{Context, anyhow};
use cap_recording::feeds::{
self,
camera::{CameraFeed, RawCameraFrame},
use cap_recording::{
FFmpegVideoFrame,
feeds::{self, camera::CameraFeed},
};
use ffmpeg::{
format::{self, Pixel},
Expand Down Expand Up @@ -465,7 +465,7 @@ impl Renderer {
window: WebviewWindow,
default_state: CameraPreviewState,
mut reconfigure: broadcast::Receiver<ReconfigureEvent>,
camera_rx: flume::Receiver<RawCameraFrame>,
camera_rx: flume::Receiver<FFmpegVideoFrame>,
) {
let mut resampler_frame = Cached::default();
let Ok(mut scaler) = scaling::Context::get(
Expand Down Expand Up @@ -496,7 +496,7 @@ impl Renderer {
} {
match event {
Ok(frame) => {
let aspect_ratio = frame.frame.width() as f32 / frame.frame.height() as f32;
let aspect_ratio = frame.inner.width() as f32 / frame.inner.height() as f32;
self.sync_ratio_uniform_and_resize_window_to_it(&window, &state, aspect_ratio);

if let Ok(surface) = self.surface.get_current_texture().map_err(|err| {
Expand All @@ -509,16 +509,16 @@ impl Renderer {
.get_or_init((output_width, output_height), frame::Video::empty);

scaler.cached(
frame.frame.format(),
frame.frame.width(),
frame.frame.height(),
frame.inner.format(),
frame.inner.width(),
frame.inner.height(),
format::Pixel::RGBA,
output_width,
output_height,
ffmpeg::software::scaling::flag::Flags::FAST_BILINEAR,
);

if let Err(err) = scaler.run(&frame.frame, resampler_frame) {
if let Err(err) = scaler.run(&frame.inner, resampler_frame) {
error!("Error rescaling frame with ffmpeg: {err:?}");
continue;
}
Expand Down
8 changes: 4 additions & 4 deletions apps/desktop/src-tauri/src/camera_legacy.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
use cap_recording::feeds::camera::RawCameraFrame;
use cap_recording::FFmpegVideoFrame;
use flume::Sender;
use tokio_util::sync::CancellationToken;

use crate::frame_ws::{WSFrame, create_frame_ws};

pub async fn create_camera_preview_ws() -> (Sender<RawCameraFrame>, u16, CancellationToken) {
let (camera_tx, mut _camera_rx) = flume::bounded::<RawCameraFrame>(4);
pub async fn create_camera_preview_ws() -> (Sender<FFmpegVideoFrame>, u16, CancellationToken) {
let (camera_tx, mut _camera_rx) = flume::bounded::<FFmpegVideoFrame>(4);
let (_camera_tx, camera_rx) = flume::bounded::<WSFrame>(4);
std::thread::spawn(move || {
use ffmpeg::format::Pixel;

let mut converter: Option<(Pixel, ffmpeg::software::scaling::Context)> = None;

while let Ok(raw_frame) = _camera_rx.recv() {
let mut frame = raw_frame.frame;
let mut frame = raw_frame.inner;

if frame.format() != Pixel::RGBA || frame.width() > 1280 || frame.height() > 720 {
let converter = match &mut converter {
Expand Down
4 changes: 3 additions & 1 deletion apps/desktop/src-tauri/src/deeplink_actions.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use cap_recording::{RecordingMode, feeds::camera::DeviceOrModelID, sources::ScreenCaptureTarget};
use cap_recording::{
RecordingMode, feeds::camera::DeviceOrModelID, sources::screen_capture::ScreenCaptureTarget,
};
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
use tauri::{AppHandle, Manager, Url};
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ use cap_recording::{
camera::{CameraFeed, DeviceOrModelID},
microphone::{self, MicrophoneFeed},
},
sources::ScreenCaptureTarget,
sources::screen_capture::ScreenCaptureTarget,
};
use cap_rendering::{ProjectRecordingsMeta, RenderedFrame};
use clipboard_rs::common::RustImage;
Expand Down
115 changes: 61 additions & 54 deletions apps/desktop/src-tauri/src/recording.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ use cap_recording::{
RecordingError, RecordingMode,
feeds::{camera, microphone},
instant_recording,
sources::{CaptureDisplay, CaptureWindow, ScreenCaptureTarget, screen_capture},
sources::{
screen_capture,
screen_capture::{CaptureDisplay, CaptureWindow, ScreenCaptureTarget},
},
studio_recording,
};
use cap_rendering::ProjectRecordingsMeta;
Expand Down Expand Up @@ -79,17 +82,19 @@ impl InProgressRecording {
}

pub async fn pause(&self) -> Result<(), RecordingError> {
match self {
Self::Instant { handle, .. } => handle.pause().await,
Self::Studio { handle, .. } => handle.pause().await,
}
todo!()
// match self {
// Self::Instant { handle, .. } => handle.pause().await,
// Self::Studio { handle, .. } => handle.pause().await,
// }
}
Comment on lines 84 to 90
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Blocking: todo!() in pause/resume/cancel will panic at runtime

These commands are exposed and used; hitting them panics. Implement by delegating to actor handles. Also unify return types to anyhow::Result<()> for consistency with actor APIs.

Apply:

-    pub async fn pause(&self) -> Result<(), RecordingError> {
-        todo!()
-        // match self {
-        //     Self::Instant { handle, .. } => handle.pause().await,
-        //     Self::Studio { handle, .. } => handle.pause().await,
-        // }
-    }
+    pub async fn pause(&self) -> anyhow::Result<()> {
+        match self {
+            Self::Instant { handle, .. } => handle.pause().await,
+            Self::Studio { handle, .. } => handle.pause().await,
+        }
+    }
 
-    pub async fn resume(&self) -> Result<(), String> {
-        todo!()
-        // match self {
-        //     Self::Instant { handle, .. } => handle.resume().await.map_err(|e| e.to_string()),
-        //     Self::Studio { handle, .. } => handle.resume().await.map_err(|e| e.to_string()),
-        // }
-    }
+    pub async fn resume(&self) -> anyhow::Result<()> {
+        match self {
+            Self::Instant { handle, .. } => handle.resume().await,
+            Self::Studio { handle, .. } => handle.resume().await,
+        }
+    }
 
-    pub async fn cancel(self) -> Result<(), RecordingError> {
-        todo!()
-        // match self {
-        //     Self::Instant { handle, .. } => handle.cancel().await,
-        //     Self::Studio { handle, .. } => handle.cancel().await,
-        // }
-    }
+    pub async fn cancel(self) -> anyhow::Result<()> {
+        match self {
+            Self::Instant { handle, .. } => handle.cancel().await,
+            Self::Studio { handle, .. } => handle.cancel().await,
+        }
+    }

Also applies to: 91-97, 138-145


pub async fn resume(&self) -> Result<(), String> {
match self {
Self::Instant { handle, .. } => handle.resume().await.map_err(|e| e.to_string()),
Self::Studio { handle, .. } => handle.resume().await.map_err(|e| e.to_string()),
}
todo!()
// match self {
// Self::Instant { handle, .. } => handle.resume().await.map_err(|e| e.to_string()),
// Self::Studio { handle, .. } => handle.resume().await.map_err(|e| e.to_string()),
// }
}

pub fn recording_dir(&self) -> &PathBuf {
Expand All @@ -99,7 +104,7 @@ impl InProgressRecording {
}
}

pub async fn stop(self) -> Result<CompletedRecording, RecordingError> {
pub async fn stop(self) -> anyhow::Result<CompletedRecording> {
Ok(match self {
Self::Instant {
handle,
Expand All @@ -124,13 +129,21 @@ impl InProgressRecording {
})
}

pub async fn cancel(self) -> Result<(), RecordingError> {
pub fn done_fut(&self) -> cap_recording::DoneFut {
match self {
Self::Instant { handle, .. } => handle.cancel().await,
Self::Studio { handle, .. } => handle.cancel().await,
Self::Instant { handle, .. } => handle.done_fut(),
Self::Studio { handle, .. } => handle.done_fut(),
}
}

pub async fn cancel(self) -> Result<(), RecordingError> {
todo!()
// match self {
// Self::Instant { handle, .. } => handle.cancel().await,
// Self::Studio { handle, .. } => handle.cancel().await,
// }
}

pub fn mode(&self) -> RecordingMode {
match self {
Self::Instant { .. } => RecordingMode::Instant,
Expand All @@ -147,7 +160,7 @@ pub enum CompletedRecording {
video_upload_info: VideoUploadInfo,
},
Studio {
recording: studio_recording::CompletedStudioRecording,
recording: studio_recording::CompletedRecording,
target_name: String,
},
}
Expand Down Expand Up @@ -406,14 +419,13 @@ pub async fn start_recording(
Err(SendError::HandlerError(camera::LockFeedError::NoInput)) => None,
Err(e) => return Err(e.to_string()),
};

#[cfg(target_os = "macos")]
let shareable_content = crate::platform::get_shareable_content()
.await
.map_err(|e| format!("GetShareableContent: {e}"))?
.ok_or_else(|| format!("GetShareableContent/NotAvailable"))?;

let (actor, actor_done_rx) = match inputs.mode {
let actor = match inputs.mode {
RecordingMode::Studio => {
let mut builder = studio_recording::Actor::builder(
recording_dir.clone(),
Expand All @@ -434,7 +446,7 @@ pub async fn start_recording(
builder = builder.with_mic_feed(mic_feed);
}

let (handle, actor_done_rx) = builder
let handle = builder
.build(
#[cfg(target_os = "macos")]
shareable_content,
Expand All @@ -445,15 +457,12 @@ pub async fn start_recording(
e.to_string()
})?;

(
InProgressRecording::Studio {
handle,
target_name,
inputs,
recording_dir: recording_dir.clone(),
},
actor_done_rx,
)
InProgressRecording::Studio {
handle,
target_name,
inputs,
recording_dir: recording_dir.clone(),
}
}
RecordingMode::Instant => {
let Some(video_upload_info) = video_upload_info.clone() else {
Expand All @@ -470,7 +479,7 @@ pub async fn start_recording(
builder = builder.with_mic_feed(mic_feed);
}

let (handle, actor_done_rx) = builder
let handle = builder
.build(
#[cfg(target_os = "macos")]
shareable_content,
Expand All @@ -481,32 +490,31 @@ pub async fn start_recording(
e.to_string()
})?;

(
InProgressRecording::Instant {
handle,
progressive_upload,
video_upload_info,
target_name,
inputs,
recording_dir: recording_dir.clone(),
},
actor_done_rx,
)
InProgressRecording::Instant {
handle,
progressive_upload,
video_upload_info,
target_name,
inputs,
recording_dir: recording_dir.clone(),
}
}
};

let done_fut = actor.done_fut();

state.set_current_recording(actor);

Ok::<_, String>(actor_done_rx)
Ok::<_, String>(done_fut)
}
})
.await
.map_err(|e| format!("Failed to spawn recording actor: {e}"))?
}
.await;

let actor_done_rx = match spawn_actor_res {
Ok(rx) => rx,
let actor_done_fut = match spawn_actor_res {
Ok(fut) => fut,
Err(e) => {
let _ = RecordingEvent::Failed { error: e.clone() }.emit(&app);

Expand Down Expand Up @@ -537,22 +545,25 @@ pub async fn start_recording(
let state_mtx = Arc::clone(&state_mtx);
async move {
fail!("recording::wait_actor_done");
let res = actor_done_rx.await;
let res = actor_done_fut.await;
info!("recording wait actor done: {:?}", &res);
match res {
Ok(Ok(_)) => {
Ok(()) => {
let _ = finish_upload_tx.send(());
let _ = RecordingEvent::Stopped.emit(&app);
}
Ok(Err(e)) => {
Err(e) => {
let mut state = state_mtx.write().await;

let _ = RecordingEvent::Failed { error: e.clone() }.emit(&app);
let _ = RecordingEvent::Failed {
error: e.to_string(),
}
.emit(&app);

let mut dialog = MessageDialogBuilder::new(
app.dialog().clone(),
"An error occurred".to_string(),
e,
e.to_string(),
)
.kind(tauri_plugin_dialog::MessageDialogKind::Error);

Expand All @@ -565,10 +576,6 @@ pub async fn start_recording(
// this clears the current recording for us
handle_recording_end(app, None, &mut state).await.ok();
}
// Actor hasn't errored, it's just finished
v => {
info!("recording actor ended: {v:?}");
}
}
}
});
Expand Down Expand Up @@ -628,7 +635,7 @@ pub async fn restart_recording(app: AppHandle, state: MutableState<'_, App>) ->

let inputs = recording.inputs().clone();

let _ = recording.cancel().await;
// let _ = recording.cancel().await;

tokio::time::sleep(Duration::from_millis(1000)).await;

Expand Down Expand Up @@ -658,7 +665,7 @@ pub async fn delete_recording(app: AppHandle, state: MutableState<'_, App>) -> R
CurrentRecordingChanged.emit(&app).ok();
RecordingStopped {}.emit(&app).ok();

let _ = recording.cancel().await;
// let _ = recording.cancel().await;

std::fs::remove_dir_all(&recording_dir).ok();

Expand Down Expand Up @@ -1105,7 +1112,7 @@ fn generate_zoom_segments_from_clicks_impl(
/// Generates zoom segments based on mouse click events during recording.
/// Used during the recording completion process.
pub fn generate_zoom_segments_from_clicks(
recording: &studio_recording::CompletedStudioRecording,
recording: &studio_recording::CompletedRecording,
recordings: &ProjectRecordingsMeta,
) -> Vec<ZoomSegment> {
// Build a temporary RecordingMeta so we can use the common implementation
Expand Down Expand Up @@ -1162,7 +1169,7 @@ pub fn generate_zoom_segments_for_project(

fn project_config_from_recording(
app: &AppHandle,
completed_recording: &studio_recording::CompletedStudioRecording,
completed_recording: &studio_recording::CompletedRecording,
recordings: &ProjectRecordingsMeta,
default_config: Option<ProjectConfiguration>,
) -> ProjectConfiguration {
Expand Down
4 changes: 3 additions & 1 deletion apps/desktop/src-tauri/src/recording_settings.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use cap_recording::{RecordingMode, feeds::camera::DeviceOrModelID, sources::ScreenCaptureTarget};
use cap_recording::{
RecordingMode, feeds::camera::DeviceOrModelID, sources::screen_capture::ScreenCaptureTarget,
};
use serde_json::json;
use tauri::{AppHandle, Wry};
use tauri_plugin_store::StoreExt;
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src-tauri/src/thumbnails/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use cap_recording::sources::{list_displays, list_windows};
use cap_recording::sources::screen_capture::{list_displays, list_windows};
use serde::{Deserialize, Serialize};
use specta::Type;
use tracing::*;
Expand Down
Loading
Loading