Skip to content

Commit ebb28ec

Browse files
Camera feed actor (#946)
* wip * vibe code it * wip * make recording work * handle InputConnected in connecting Lock --------- Co-authored-by: Brendan Allan <brendonovich@outlook.com>
1 parent b03a60e commit ebb28ec

12 files changed

Lines changed: 485 additions & 415 deletions

File tree

apps/desktop/src-tauri/src/camera.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use anyhow::{Context, anyhow};
2-
use cap_recording::feeds::RawCameraFrame;
2+
use cap_recording::feeds::camera::RawCameraFrame;
33
use ffmpeg::{
44
format::{self, Pixel},
55
frame,

apps/desktop/src-tauri/src/camera_legacy.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use cap_recording::feeds::RawCameraFrame;
1+
use cap_recording::feeds::camera::RawCameraFrame;
22
use flume::Sender;
33
use tokio_util::sync::CancellationToken;
44

apps/desktop/src-tauri/src/deeplink_actions.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use cap_recording::{RecordingMode, feeds::DeviceOrModelID, sources::ScreenCaptureTarget};
1+
use cap_recording::{RecordingMode, feeds::camera::DeviceOrModelID, sources::ScreenCaptureTarget};
22
use serde::{Deserialize, Serialize};
33
use std::path::{Path, PathBuf};
44
use tauri::{AppHandle, Manager, Url};
@@ -114,7 +114,7 @@ impl DeepLinkAction {
114114
} => {
115115
let state = app.state::<ArcLock<App>>();
116116

117-
crate::set_camera_input(app.clone(), state.clone(), camera).await?;
117+
crate::set_camera_input(state.clone(), camera).await?;
118118
crate::set_mic_input(state.clone(), mic_label).await?;
119119

120120
let capture_target: ScreenCaptureTarget = match capture_mode {

apps/desktop/src-tauri/src/hotkeys.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ pub fn init(app: &AppHandle) {
101101
)
102102
.unwrap();
103103

104-
let mut store = match HotkeysStore::get(app) {
104+
let store = match HotkeysStore::get(app) {
105105
Ok(Some(s)) => s,
106106
Ok(None) => HotkeysStore::default(),
107107
Err(e) => {

apps/desktop/src-tauri/src/lib.rs

Lines changed: 41 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ use cap_project::{
3434
};
3535
use cap_recording::{
3636
feeds::{
37-
self, CameraFeed, DeviceOrModelID, RawCameraFrame,
37+
self,
38+
camera::{CameraFeed, DeviceOrModelID, RawCameraFrame},
3839
microphone::{self, MicrophoneFeed},
3940
},
4041
sources::ScreenCaptureTarget,
@@ -67,7 +68,6 @@ use std::{
6768
process::Command,
6869
str::FromStr,
6970
sync::Arc,
70-
time::Duration,
7171
};
7272
use tauri::{AppHandle, Manager, State, Window, WindowEvent};
7373
use tauri_plugin_deep_link::DeepLinkExt;
@@ -77,10 +77,7 @@ use tauri_plugin_notification::{NotificationExt, PermissionState};
7777
use tauri_plugin_opener::OpenerExt;
7878
use tauri_plugin_shell::ShellExt;
7979
use tauri_specta::Event;
80-
use tokio::{
81-
sync::{Mutex, RwLock, mpsc},
82-
time::timeout,
83-
};
80+
use tokio::sync::RwLock;
8481
use tracing::{error, trace};
8582
use upload::{S3UploadMeta, create_or_get_video, upload_image, upload_video};
8683
use web_api::ManagerExt as WebManagerExt;
@@ -105,19 +102,17 @@ pub struct App {
105102
#[deprecated = "can be removed when native camera preview is ready"]
106103
camera_ws_port: u16,
107104
#[serde(skip)]
108-
camera_feed: Option<Arc<Mutex<CameraFeed>>>,
109-
#[serde(skip)]
110105
camera_preview: CameraPreviewManager,
111106
#[serde(skip)]
112-
camera_feed_initialization: Option<mpsc::Sender<()>>,
113-
#[serde(skip)]
114107
handle: AppHandle,
115108
#[serde(skip)]
116109
recording_state: RecordingState,
117110
#[serde(skip)]
118111
recording_logging_handle: LoggingHandle,
119112
#[serde(skip)]
120113
mic_feed: ActorRef<feeds::microphone::MicrophoneFeed>,
114+
#[serde(skip)]
115+
camera_feed: ActorRef<feeds::camera::CameraFeed>,
121116
server_url: String,
122117
}
123118

@@ -245,89 +240,29 @@ async fn set_mic_input(state: MutableState<'_, App>, label: Option<String>) -> R
245240
#[tauri::command]
246241
#[specta::specta]
247242
async fn set_camera_input(
248-
app_handle: AppHandle,
249243
state: MutableState<'_, App>,
250244
id: Option<DeviceOrModelID>,
251-
) -> Result<bool, String> {
252-
let mut app = state.write().await;
245+
) -> Result<(), String> {
246+
let camera_feed = state.read().await.camera_feed.clone();
253247

254-
match (id, app.camera_feed.as_ref()) {
255-
(Some(id), Some(camera_feed)) => {
248+
match id {
249+
None => {
256250
camera_feed
257-
.lock()
258-
.await
259-
.switch_cameras(id)
251+
.ask(feeds::camera::RemoveInput)
260252
.await
261253
.map_err(|e| e.to_string())?;
262-
Ok(true)
263-
}
264-
(Some(id), None) => {
265-
let (shutdown_tx, mut shutdown_rx) = mpsc::channel(1);
266-
if let Some(cancel) = app.camera_feed_initialization.as_ref() {
267-
// Ask currently running setup to abort
268-
cancel.send(()).await.ok();
269-
270-
// We can assume a window was already initialized.
271-
// Stop it so we can recreate it with the correct `camera_tx`
272-
if let Some(win) = CapWindowId::Camera.get(&app_handle) {
273-
win.close().unwrap(); // TODO: Error handling
274-
};
275-
} else {
276-
app.camera_feed_initialization = Some(shutdown_tx);
277-
}
278-
279-
ShowCapWindow::Camera.show(&app_handle).await.unwrap();
280-
if let Some(win) = CapWindowId::Main.get(&app_handle) {
281-
win.set_focus().ok();
282-
};
283-
284-
let camera_tx = if GeneralSettingsStore::get(&app_handle)
285-
.ok()
286-
.and_then(|v| v.map(|v| v.enable_native_camera_preview))
287-
.unwrap_or_default()
288-
{
289-
app.camera_preview.attach()
290-
} else {
291-
app.camera_tx.clone()
292-
};
293-
294-
drop(app);
295-
296-
let fut = CameraFeed::init(id);
297-
298-
tokio::select! {
299-
result = fut => {
300-
let feed = result.map_err(|err| err.to_string())?;
301-
let mut app = state.write().await;
302-
303-
if let Some(cancel) = app.camera_feed_initialization.take() {
304-
cancel.send(()).await.ok();
305-
}
306-
307-
if app.camera_feed.is_none() {
308-
feed.attach(camera_tx);
309-
app.camera_feed = Some(Arc::new(Mutex::new(feed)));
310-
Ok(true)
311-
} else {
312-
Ok(false)
313-
}
314-
}
315-
_ = shutdown_rx.recv() => {
316-
Ok(false)
317-
}
318-
}
319254
}
320-
(None, _) => {
321-
if let Some(cancel) = app.camera_feed_initialization.take() {
322-
cancel.send(()).await.ok();
323-
}
324-
app.camera_feed.take();
325-
if let Some(win) = CapWindowId::Camera.get(&app_handle) {
326-
win.close().ok();
327-
}
328-
Ok(true)
255+
Some(id) => {
256+
camera_feed
257+
.ask(feeds::camera::SetInput { id })
258+
.await
259+
.map_err(|e| e.to_string())?
260+
.await
261+
.map_err(|e| e.to_string())?;
329262
}
330263
}
264+
265+
Ok(())
331266
}
332267

333268
#[derive(specta::Type, Serialize, tauri_specta::Event, Clone)]
@@ -1994,6 +1929,23 @@ pub async fn run(recording_logging_handle: LoggingHandle) {
19941929

19951930
let (mic_samples_tx, mic_samples_rx) = flume::bounded(8);
19961931

1932+
let camera_feed = {
1933+
let (error_tx, error_rx) = flume::bounded(1);
1934+
1935+
let mic_feed = CameraFeed::spawn(CameraFeed::new(error_tx));
1936+
1937+
// TODO: make this part of a global actor one day
1938+
tokio::spawn(async move {
1939+
let Ok(err) = error_rx.recv_async().await else {
1940+
return;
1941+
};
1942+
1943+
error!("Camera feed actor error: {err:?}");
1944+
});
1945+
1946+
mic_feed
1947+
};
1948+
19971949
let mic_feed = {
19981950
let (error_tx, error_rx) = flume::bounded(1);
19991951

@@ -2108,12 +2060,11 @@ pub async fn run(recording_logging_handle: LoggingHandle) {
21082060
camera_tx,
21092061
camera_ws_port,
21102062
handle: app.clone(),
2111-
camera_feed: None,
2112-
camera_feed_initialization: None,
21132063
camera_preview: CameraPreviewManager::new(&app),
21142064
recording_state: RecordingState::None,
21152065
recording_logging_handle,
21162066
mic_feed,
2067+
camera_feed,
21172068
server_url: GeneralSettingsStore::get(&app)
21182069
.ok()
21192070
.flatten()
@@ -2198,7 +2149,10 @@ pub async fn run(recording_logging_handle: LoggingHandle) {
21982149
if !app_state.is_recording_active_or_pending() {
21992150
let _ =
22002151
app_state.mic_feed.ask(microphone::RemoveInput).await;
2201-
app_state.camera_feed.take();
2152+
let _ = app_state
2153+
.camera_feed
2154+
.ask(feeds::camera::RemoveInput)
2155+
.await;
22022156
}
22032157
});
22042158
}

apps/desktop/src-tauri/src/recording.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use cap_project::{
66
};
77
use cap_recording::{
88
CompletedStudioRecording, RecordingError, RecordingMode, StudioRecordingHandle,
9-
feeds::{CameraFeed, microphone},
9+
feeds::{camera, microphone},
1010
instant_recording::{CompletedInstantRecording, InstantRecordingHandle},
1111
sources::{CaptureDisplay, CaptureWindow, ScreenCaptureTarget, screen_capture},
1212
};
@@ -180,7 +180,7 @@ pub async fn list_capture_windows() -> Vec<CaptureWindow> {
180180
#[tauri::command(async)]
181181
#[specta::specta]
182182
pub fn list_cameras() -> Vec<cap_camera::CameraInfo> {
183-
CameraFeed::list_cameras()
183+
cap_camera::list_cameras().collect()
184184
}
185185

186186
#[derive(Deserialize, Type, Clone, Debug)]
@@ -359,10 +359,17 @@ pub async fn start_recording(
359359
Err(e) => return Err(e.to_string()),
360360
};
361361

362+
let camera_feed = match state.camera_feed.ask(camera::Lock).await {
363+
Ok(lock) => Some(Arc::new(lock)),
364+
Err(SendError::HandlerError(camera::LockFeedError::NoInput)) => None,
365+
Err(e) => return Err(e.to_string()),
366+
};
367+
362368
let base_inputs = cap_recording::RecordingBaseInputs {
363369
capture_target: inputs.capture_target.clone(),
364370
capture_system_audio: inputs.capture_system_audio,
365371
mic_feed,
372+
camera_feed,
366373
};
367374

368375
let (actor, actor_done_rx) = match inputs.mode {
@@ -371,7 +378,6 @@ pub async fn start_recording(
371378
id.clone(),
372379
recording_dir.clone(),
373380
base_inputs,
374-
state.camera_feed.clone(),
375381
general_settings
376382
.map(|s| s.custom_cursor_capture)
377383
.unwrap_or_default(),
@@ -653,7 +659,7 @@ async fn handle_recording_end(
653659
let _ = v.close();
654660
}
655661
let _ = app.mic_feed.ask(microphone::RemoveInput).await;
656-
app.camera_feed.take();
662+
let _ = app.camera_feed.ask(camera::RemoveInput).await;
657663
if let Some(win) = CapWindowId::Camera.get(&handle) {
658664
win.close().ok();
659665
}

0 commit comments

Comments
 (0)