Skip to content

Commit 9edf1ce

Browse files
feat: Numerous camera fixes (pausing, stopping, UX bits, etc)
2 parents 8a922a4 + c644e6d commit 9edf1ce

File tree

10 files changed

+482
-192
lines changed

10 files changed

+482
-192
lines changed

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ use std::path::{Path, PathBuf};
66
use tauri::{AppHandle, Manager, Url};
77
use tracing::trace;
88

9-
use crate::{App, ArcLock, recording::StartRecordingInputs, windows::ShowCapWindow};
9+
use crate::{
10+
App, ArcLock, apply_camera_input, apply_mic_input, recording::StartRecordingInputs,
11+
windows::ShowCapWindow,
12+
};
1013

1114
#[derive(Debug, Serialize, Deserialize)]
1215
#[serde(rename_all = "snake_case")]
@@ -116,8 +119,8 @@ impl DeepLinkAction {
116119
} => {
117120
let state = app.state::<ArcLock<App>>();
118121

119-
crate::set_camera_input(app.clone(), state.clone(), camera).await?;
120-
crate::set_mic_input(state.clone(), mic_label).await?;
122+
apply_camera_input(app.clone(), state.clone(), camera).await?;
123+
apply_mic_input(state.clone(), mic_label).await?;
121124

122125
let capture_target: ScreenCaptureTarget = match capture_mode {
123126
CaptureMode::Screen(name) => cap_recording::screen_capture::list_displays()

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

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,13 @@ impl App {
331331
#[specta::specta]
332332
#[instrument(skip(state))]
333333
async fn set_mic_input(state: MutableState<'_, App>, label: Option<String>) -> Result<(), String> {
334+
apply_mic_input(state, label).await
335+
}
336+
337+
pub(crate) async fn apply_mic_input(
338+
state: MutableState<'_, App>,
339+
label: Option<String>,
340+
) -> Result<(), String> {
334341
let (mic_feed, studio_handle, current_label) = {
335342
let app = state.read().await;
336343
let handle = match app.current_recording() {
@@ -414,6 +421,14 @@ async fn set_camera_input(
414421
app_handle: AppHandle,
415422
state: MutableState<'_, App>,
416423
id: Option<DeviceOrModelID>,
424+
) -> Result<(), String> {
425+
apply_camera_input(app_handle, state, id).await
426+
}
427+
428+
pub(crate) async fn apply_camera_input(
429+
app_handle: AppHandle,
430+
state: MutableState<'_, App>,
431+
id: Option<DeviceOrModelID>,
417432
) -> Result<(), String> {
418433
let app = state.read().await;
419434
let camera_feed = app.camera_feed.clone();
@@ -2543,8 +2558,8 @@ pub async fn run(recording_logging_handle: LoggingHandle, logs_dir: PathBuf) {
25432558
.flatten()
25442559
.unwrap_or_default();
25452560

2546-
let _ = set_mic_input(app.state(), settings.mic_name).await;
2547-
let _ = set_camera_input(app.clone(), app.state(), settings.camera_id).await;
2561+
let _ = apply_mic_input(app.state(), settings.mic_name).await;
2562+
let _ = apply_camera_input(app.clone(), app.state(), settings.camera_id).await;
25482563

25492564
let _ = start_recording(app.clone(), app.state(), {
25502565
recording::StartRecordingInputs {

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

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ use crate::{
4747
App, CurrentRecordingChanged, MutableState, NewStudioRecordingAdded, RecordingState,
4848
RecordingStopped, VideoUploadInfo,
4949
api::PresignedS3PutRequestMethod,
50+
apply_camera_input, apply_mic_input,
5051
audio::AppSounds,
5152
auth::AuthStore,
5253
create_screenshot,
@@ -55,6 +56,7 @@ use crate::{
5556
},
5657
open_external_link,
5758
presets::PresetsStore,
59+
recording_settings::RecordingSettingsStore,
5860
thumbnails::*,
5961
upload::{
6062
InstantMultipartUpload, build_video_meta, compress_image, create_or_get_video, upload_video,
@@ -349,6 +351,41 @@ pub enum RecordingAction {
349351
UpgradeRequired,
350352
}
351353

354+
async fn restore_inputs_from_store_if_missing(app: &AppHandle, state: &MutableState<'_, App>) {
355+
let guard = state.read().await;
356+
let recording_active = !matches!(guard.recording_state, RecordingState::None);
357+
let needs_mic = guard.selected_mic_label.is_none();
358+
let needs_camera = guard.selected_camera_id.is_none();
359+
drop(guard);
360+
361+
if recording_active || (!needs_mic && !needs_camera) {
362+
return;
363+
}
364+
365+
let settings = match RecordingSettingsStore::get(app) {
366+
Ok(Some(settings)) => settings,
367+
Ok(None) => return,
368+
Err(err) => {
369+
warn!(%err, "Failed to load recording settings while restoring inputs");
370+
return;
371+
}
372+
};
373+
374+
if let Some(mic) = settings.mic_name.clone().filter(|_| needs_mic) {
375+
match apply_mic_input(app.state(), Some(mic)).await {
376+
Err(err) => warn!(%err, "Failed to restore microphone input"),
377+
Ok(_) => {}
378+
}
379+
}
380+
381+
if let Some(camera) = settings.camera_id.clone().filter(|_| needs_camera) {
382+
match apply_camera_input(app.clone(), app.state(), Some(camera)).await {
383+
Err(err) => warn!(%err, "Failed to restore camera input"),
384+
Ok(_) => {}
385+
}
386+
}
387+
}
388+
352389
#[tauri::command]
353390
#[specta::specta]
354391
#[tracing::instrument(name = "recording", skip_all)]
@@ -357,10 +394,28 @@ pub async fn start_recording(
357394
state_mtx: MutableState<'_, App>,
358395
inputs: StartRecordingInputs,
359396
) -> Result<RecordingAction, String> {
397+
restore_inputs_from_store_if_missing(&app, &state_mtx).await;
398+
360399
if !matches!(state_mtx.read().await.recording_state, RecordingState::None) {
361400
return Err("Recording already in progress".to_string());
362401
}
363402

403+
let has_camera_selected = {
404+
let guard = state_mtx.read().await;
405+
guard.selected_camera_id.is_some()
406+
};
407+
let camera_window_open = CapWindowId::Camera.get(&app).is_some();
408+
let should_open_camera_preview =
409+
matches!(inputs.mode, RecordingMode::Instant) && has_camera_selected && !camera_window_open;
410+
411+
if should_open_camera_preview {
412+
ShowCapWindow::Camera
413+
.show(&app)
414+
.await
415+
.map_err(|err| error!("Failed to show camera preview window: {err}"))
416+
.ok();
417+
}
418+
364419
let id = uuid::Uuid::new_v4().to_string();
365420
let general_settings = GeneralSettingsStore::get(&app).ok().flatten();
366421
let general_settings = general_settings.as_ref();

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

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ use tokio::sync::RwLock;
2121
use tracing::{debug, error, instrument, warn};
2222

2323
use crate::{
24-
App, ArcLock, RequestScreenCapturePrewarm, fake_window,
24+
App, ArcLock, RequestScreenCapturePrewarm, apply_camera_input, apply_mic_input, fake_window,
2525
general_settings::{self, AppTheme, GeneralSettingsStore},
2626
permissions,
27-
recording_settings::RecordingTargetMode,
27+
recording_settings::{RecordingSettingsStore, RecordingTargetMode},
2828
target_select_overlay::WindowFocusManager,
2929
window_exclusion::WindowExclusion,
3030
};
@@ -282,6 +282,8 @@ impl ShowCapWindow {
282282
crate::platform::set_window_level(window.as_ref().window(), 50);
283283
}
284284

285+
restore_recording_inputs_if_idle(app);
286+
285287
#[cfg(target_os = "macos")]
286288
{
287289
let app_handle = app.clone();
@@ -797,6 +799,48 @@ impl ShowCapWindow {
797799
}
798800
}
799801

802+
fn restore_recording_inputs_if_idle(app: &AppHandle<Wry>) {
803+
let settings = match RecordingSettingsStore::get(app) {
804+
Ok(Some(settings)) => settings,
805+
Ok(None) => return,
806+
Err(err) => {
807+
warn!(%err, "Failed to load recording settings while restoring inputs");
808+
return;
809+
}
810+
};
811+
812+
let mic_name = settings.mic_name.clone();
813+
let camera_id = settings.camera_id.clone();
814+
815+
if mic_name.is_none() && camera_id.is_none() {
816+
return;
817+
}
818+
819+
let app_handle = app.clone();
820+
let state = app_handle.state::<ArcLock<App>>();
821+
let app_state = state.inner().clone();
822+
823+
tauri::async_runtime::spawn(async move {
824+
if app_state.read().await.is_recording_active_or_pending() {
825+
return;
826+
}
827+
828+
if let Some(mic) = mic_name {
829+
match apply_mic_input(app_handle.state(), Some(mic)).await {
830+
Err(err) => warn!(%err, "Failed to restore microphone input"),
831+
Ok(_) => {}
832+
}
833+
}
834+
835+
if let Some(camera) = camera_id {
836+
match apply_camera_input(app_handle.clone(), app_handle.state(), Some(camera)).await {
837+
Err(err) => warn!(%err, "Failed to restore camera input"),
838+
Ok(_) => {}
839+
}
840+
}
841+
});
842+
}
843+
800844
#[cfg(target_os = "macos")]
801845
fn add_traffic_lights(window: &WebviewWindow<Wry>, controls_inset: Option<LogicalPosition<f64>>) {
802846
use crate::platform::delegates;

apps/desktop/src/routes/camera.tsx

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import {
2222
import { createStore } from "solid-js/store";
2323
import { generalSettingsStore } from "~/store";
2424
import { createTauriEventListener } from "~/utils/createEventListener";
25-
import { createCameraMutation } from "~/utils/queries";
2625
import { createImageDataWS, createLazySignal } from "~/utils/socket";
2726
import { commands, events } from "~/utils/tauri";
2827
import {
@@ -88,8 +87,6 @@ function NativeCameraPreviewPage(props: { disconnected: Accessor<boolean> }) {
8887
commands.awaitCameraPreviewReady(),
8988
);
9089

91-
const setCamera = createCameraMutation();
92-
9390
return (
9491
<div
9592
data-tauri-drag-region
@@ -101,7 +98,7 @@ function NativeCameraPreviewPage(props: { disconnected: Accessor<boolean> }) {
10198
<div class="h-13">
10299
<div class="flex flex-row justify-center items-center">
103100
<div class="flex flex-row gap-[0.25rem] p-[0.25rem] opacity-0 group-hover:opacity-100 translate-y-2 group-hover:translate-y-0 rounded-xl transition-[opacity,transform] bg-gray-1 border border-white-transparent-20 text-gray-10">
104-
<ControlButton onClick={() => setCamera.mutate(null)}>
101+
<ControlButton onClick={() => void getCurrentWindow().close()}>
105102
<IconCapCircleX class="size-5.5" />
106103
</ControlButton>
107104
<ControlButton
@@ -268,8 +265,6 @@ function LegacyCameraPreviewPage(props: { disconnected: Accessor<boolean> }) {
268265

269266
let cameraCanvasRef: HTMLCanvasElement | undefined;
270267

271-
const setCamera = createCameraMutation();
272-
273268
createEffect(
274269
on(
275270
() => rawOptions.cameraLabel,
@@ -294,7 +289,7 @@ function LegacyCameraPreviewPage(props: { disconnected: Accessor<boolean> }) {
294289
<div class="h-14">
295290
<div class="flex flex-row justify-center items-center">
296291
<div class="flex flex-row gap-[0.25rem] p-[0.25rem] opacity-0 group-hover:opacity-100 translate-y-2 group-hover:translate-y-0 rounded-xl transition-[opacity,transform] bg-gray-1 border border-white-transparent-20 text-gray-10">
297-
<ControlButton onClick={() => setCamera.mutate(null)}>
292+
<ControlButton onClick={() => void getCurrentWindow().close()}>
298293
<IconCapCircleX class="size-5.5" />
299294
</ControlButton>
300295
<ControlButton

0 commit comments

Comments
 (0)