Skip to content

Commit d22a4d9

Browse files
committed
Restore main window inputs; refactor device locking
1 parent 1aea5a5 commit d22a4d9

3 files changed

Lines changed: 141 additions & 121 deletions

File tree

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4722,6 +4722,7 @@ pub async fn run(recording_logging_handle: LoggingHandle, logs_dir: PathBuf) {
47224722
}
47234723
CapWindowId::Main => {
47244724
let _ = window.show();
4725+
restore_main_window_inputs(app);
47254726
}
47264727
_ => {}
47274728
}
@@ -4747,6 +4748,7 @@ pub async fn run(recording_logging_handle: LoggingHandle, logs_dir: PathBuf) {
47474748
}
47484749
CapWindowId::Main => {
47494750
let _ = window.show();
4751+
restore_main_window_inputs(app);
47504752
}
47514753
_ => {}
47524754
}
@@ -5087,13 +5089,21 @@ fn restore_main_windows_if_no_editors(app: &AppHandle) {
50875089
let _ = main.show();
50885090
}
50895091

5092+
restore_main_window_inputs(app);
50905093
restore_camera_window(app);
50915094
}
50925095

50935096
spawn_on_runtime(captions::release_ml_models());
50945097
}
50955098
}
50965099

5100+
fn restore_main_window_inputs(app: &AppHandle) {
5101+
let handle = app.clone();
5102+
spawn_on_runtime(async move {
5103+
windows::restore_main_window_inputs(&handle).await;
5104+
});
5105+
}
5106+
50975107
fn restore_camera_window(app: &AppHandle) {
50985108
let should_restore_camera = app
50995109
.try_state::<ArcLock<App>>()
@@ -5142,6 +5152,7 @@ fn reopen_main_window(app: &AppHandle) {
51425152
if let Some(main) = CapWindowId::Main.get(app) {
51435153
let _ = main.show();
51445154
let _ = main.set_focus();
5155+
restore_main_window_inputs(app);
51455156
} else {
51465157
let handle = app.clone();
51475158
tokio::spawn(async move {

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

Lines changed: 129 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -557,10 +557,7 @@ fn camera_id_label(id: &camera::DeviceOrModelID) -> String {
557557
}
558558
}
559559

560-
fn camera_lock_matches_id(
561-
lock: &CameraFeedLock,
562-
selected_id: &camera::DeviceOrModelID,
563-
) -> bool {
560+
fn camera_lock_matches_id(lock: &CameraFeedLock, selected_id: &camera::DeviceOrModelID) -> bool {
564561
let camera_info = lock.camera_info();
565562
match selected_id {
566563
camera::DeviceOrModelID::DeviceID(device_id) => camera_info.device_id() == device_id,
@@ -582,7 +579,7 @@ async fn initialize_selected_camera(
582579
.await
583580
.map_err(|err| anyhow!("Failed to initialize selected camera '{label}': {err}"))?;
584581

585-
ready.await.map_err(|err| match err {
582+
ready.await.map(|_| ()).map_err(|err| match err {
586583
camera::SetInputError::DeviceNotFound => {
587584
anyhow!("Selected camera '{label}' is no longer available")
588585
}
@@ -597,9 +594,7 @@ async fn lock_initialized_camera(
597594
let label = camera_id_label(id);
598595
match camera_feed.ask(camera::Lock).await {
599596
Ok(lock) => Ok(lock),
600-
Err(kameo::error::SendError::HandlerError(
601-
camera::LockFeedError::NoInput,
602-
)) => Err(anyhow!(
597+
Err(kameo::error::SendError::HandlerError(camera::LockFeedError::NoInput)) => Err(anyhow!(
603598
"Selected camera '{label}' did not become ready after initialization"
604599
)),
605600
Err(err) => Err(anyhow!("Failed to lock selected camera '{label}': {err}")),
@@ -655,9 +650,7 @@ async fn lock_selected_camera(
655650
tokio::time::sleep(Duration::from_millis(50)).await;
656651
None
657652
}
658-
Err(kameo::error::SendError::HandlerError(
659-
camera::LockFeedError::NoInput,
660-
)) => None,
653+
Err(kameo::error::SendError::HandlerError(camera::LockFeedError::NoInput)) => None,
661654
Err(err) => {
662655
return Err(anyhow!(
663656
"Failed to lock selected camera '{}': {err}",
@@ -690,7 +683,7 @@ async fn initialize_selected_microphone(
690683
.await
691684
.map_err(|err| anyhow!("Failed to initialize selected microphone '{label}': {err}"))?;
692685

693-
ready.await.map_err(|err| match err {
686+
ready.await.map(|_| ()).map_err(|err| match err {
694687
microphone::SetInputError::DeviceNotFound => {
695688
anyhow!("Selected microphone '{label}' is no longer available")
696689
}
@@ -704,12 +697,12 @@ async fn lock_initialized_microphone(
704697
) -> anyhow::Result<microphone::MicrophoneFeedLock> {
705698
match mic_feed.ask(microphone::Lock).await {
706699
Ok(lock) => Ok(lock),
707-
Err(kameo::error::SendError::HandlerError(
708-
microphone::LockFeedError::NoInput,
709-
)) => Err(anyhow!(
710-
"Selected microphone '{label}' did not become ready after initialization"
700+
Err(kameo::error::SendError::HandlerError(microphone::LockFeedError::NoInput)) => Err(
701+
anyhow!("Selected microphone '{label}' did not become ready after initialization"),
702+
),
703+
Err(err) => Err(anyhow!(
704+
"Failed to lock selected microphone '{label}': {err}"
711705
)),
712-
Err(err) => Err(anyhow!("Failed to lock selected microphone '{label}': {err}")),
713706
}
714707
}
715708

@@ -1102,9 +1095,7 @@ pub async fn start_recording(
11021095
let inputs = inputs.clone();
11031096
async move {
11041097
fail!("recording::spawn_actor");
1105-
use kameo::error::SendError;
11061098

1107-
// Initialize camera if selected but not active
11081099
let (camera_feed_actor, selected_camera_id, selected_camera_settings) = {
11091100
let state = state_mtx.read().await;
11101101
let selected_camera_settings = state.selected_camera_id.as_ref().and_then(|id| {
@@ -1120,52 +1111,16 @@ pub async fn start_recording(
11201111
)
11211112
};
11221113

1123-
let camera_lock_result = camera_feed_actor.ask(camera::Lock).await;
1124-
1125-
let camera_feed_lock = match camera_lock_result {
1126-
Ok(lock) => Some(lock),
1127-
Err(SendError::HandlerError(camera::LockFeedError::NoInput)) => {
1128-
if let Some(id) = selected_camera_id {
1129-
info!(
1130-
"Camera selected but not initialized, initializing: {:?}",
1131-
id
1132-
);
1133-
match camera_feed_actor
1134-
.ask(camera::SetInput {
1135-
id: id.clone(),
1136-
settings: selected_camera_settings,
1137-
})
1138-
.await
1139-
{
1140-
Ok(fut) => match fut.await {
1141-
Ok(_) => match camera_feed_actor.ask(camera::Lock).await {
1142-
Ok(lock) => Some(lock),
1143-
Err(e) => {
1144-
warn!("Failed to lock camera after initialization: {}", e);
1145-
None
1146-
}
1147-
},
1148-
Err(e) => {
1149-
warn!("Failed to initialize camera: {}", e);
1150-
None
1151-
}
1152-
},
1153-
Err(e) => {
1154-
warn!("Failed to ask SetInput: {}", e);
1155-
None
1156-
}
1157-
}
1158-
} else {
1159-
None
1160-
}
1161-
}
1162-
Err(e) => return Err(anyhow!(e.to_string())),
1163-
};
1114+
let camera_feed = lock_selected_camera(
1115+
&camera_feed_actor,
1116+
selected_camera_id,
1117+
selected_camera_settings,
1118+
&inputs.capture_target,
1119+
)
1120+
.await?;
11641121

11651122
let mut state = state_mtx.write().await;
11661123

1167-
let camera_feed = camera_feed_lock.map(Arc::new);
1168-
11691124
state.camera_in_use = camera_feed.is_some();
11701125

11711126
#[cfg(target_os = "macos")]
@@ -1801,34 +1756,30 @@ async fn lock_selected_microphone(
18011756
return Ok(None);
18021757
};
18031758

1804-
match mic_feed.ask(microphone::Lock).await {
1805-
Ok(lock) => return Ok(Some(Arc::new(lock))),
1806-
Err(kameo::error::SendError::HandlerError(microphone::LockFeedError::NoInput)) => {}
1807-
Err(err) => return Err(anyhow!(err.to_string())),
1808-
}
1809-
1810-
let ready = mic_feed
1811-
.ask(microphone::SetInput {
1812-
label: label.clone(),
1813-
settings: selected_settings,
1814-
})
1815-
.await
1816-
.map_err(|err| anyhow!(err.to_string()))?;
1817-
1818-
ready.await.map_err(|err| match err {
1819-
microphone::SetInputError::DeviceNotFound => {
1820-
anyhow!("Selected microphone '{label}' is no longer available")
1759+
let existing_lock = match mic_feed.ask(microphone::Lock).await {
1760+
Ok(lock) if lock.device_name() == label => Some(lock),
1761+
Ok(lock) => {
1762+
drop(lock);
1763+
tokio::time::sleep(Duration::from_millis(50)).await;
1764+
None
18211765
}
1822-
err => anyhow!("Failed to initialize selected microphone '{label}': {err}"),
1823-
})?;
1766+
Err(kameo::error::SendError::HandlerError(microphone::LockFeedError::NoInput)) => None,
1767+
Err(err) => {
1768+
return Err(anyhow!(
1769+
"Failed to lock selected microphone '{label}': {err}"
1770+
));
1771+
}
1772+
};
18241773

1825-
match mic_feed.ask(microphone::Lock).await {
1826-
Ok(lock) => Ok(Some(Arc::new(lock))),
1827-
Err(kameo::error::SendError::HandlerError(microphone::LockFeedError::NoInput)) => Err(
1828-
anyhow!("Selected microphone '{label}' did not become ready after initialization"),
1829-
),
1830-
Err(err) => Err(anyhow!(err.to_string())),
1831-
}
1774+
let lock = if let Some(lock) = existing_lock {
1775+
lock
1776+
} else {
1777+
initialize_selected_microphone(mic_feed, &label, selected_settings).await?;
1778+
lock_initialized_microphone(mic_feed, &label).await?
1779+
};
1780+
1781+
validate_microphone_receiving(&lock, &label).await?;
1782+
Ok(Some(Arc::new(lock)))
18321783
}
18331784

18341785
fn mic_actor_not_running(err: &anyhow::Error) -> bool {
@@ -1866,6 +1817,92 @@ where
18661817
}
18671818
}
18681819

1820+
async fn cancel_discarded_recording(
1821+
app: &AppHandle,
1822+
recording: InProgressRecording,
1823+
) -> Option<String> {
1824+
match recording {
1825+
InProgressRecording::Instant {
1826+
handle,
1827+
segment_upload,
1828+
video_upload_info,
1829+
..
1830+
} => {
1831+
let video_id = video_upload_info.id;
1832+
segment_upload.handle.abort();
1833+
1834+
if let Err(err) = handle.cancel().await {
1835+
warn!("Failed to cancel instant recording while discarding: {err:#}");
1836+
}
1837+
1838+
match segment_upload.handle.await {
1839+
Ok(Ok(())) => {}
1840+
Ok(Err(err)) => warn!("Instant upload ended while discarding recording: {err}"),
1841+
Err(err) if err.is_cancelled() => {}
1842+
Err(err) => {
1843+
warn!("Failed to join instant upload while discarding recording: {err}")
1844+
}
1845+
}
1846+
1847+
crate::upload::emit_upload_complete(app, &video_id);
1848+
Some(video_id)
1849+
}
1850+
InProgressRecording::Studio { handle, .. } => {
1851+
if let Err(err) = handle.cancel().await {
1852+
warn!("Failed to cancel studio recording while discarding: {err:#}");
1853+
}
1854+
1855+
None
1856+
}
1857+
}
1858+
}
1859+
1860+
async fn remove_recording_dir(recording_dir: &Path) -> Result<(), String> {
1861+
match tokio::fs::remove_dir_all(recording_dir).await {
1862+
Ok(()) => Ok(()),
1863+
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(()),
1864+
Err(err) => Err(format!("Failed to delete recording files: {err}")),
1865+
}
1866+
}
1867+
1868+
async fn delete_remote_instant_video(app: &AppHandle, video_id: &str) -> Result<(), String> {
1869+
let response = app
1870+
.authed_api_request(
1871+
format!("/api/desktop/video/delete?videoId={video_id}"),
1872+
|client, url| client.delete(url),
1873+
)
1874+
.await
1875+
.map_err(|err| format!("Failed to delete instant recording: {err}"))?;
1876+
1877+
let status = response.status();
1878+
if status.is_success() || status == reqwest::StatusCode::NOT_FOUND {
1879+
return Ok(());
1880+
}
1881+
1882+
let body = response
1883+
.text()
1884+
.await
1885+
.unwrap_or_else(|err| format!("Failed to read response body: {err}"));
1886+
1887+
Err(format!(
1888+
"Failed to delete instant recording {video_id}: {status}: {body}"
1889+
))
1890+
}
1891+
1892+
async fn discard_recording(app: &AppHandle, recording: InProgressRecording) -> Result<(), String> {
1893+
let recording_dir = recording.recording_dir().clone();
1894+
let video_id = cancel_discarded_recording(app, recording).await;
1895+
let local_delete = remove_recording_dir(&recording_dir).await;
1896+
let remote_delete = if let Some(video_id) = video_id {
1897+
delete_remote_instant_video(app, &video_id).await
1898+
} else {
1899+
Ok(())
1900+
};
1901+
1902+
remote_delete?;
1903+
local_delete
1904+
}
1905+
18691906
#[tauri::command]
18701907
#[specta::specta]
18711908
#[instrument(skip(app, state))]
@@ -1910,7 +1947,7 @@ pub async fn restart_recording(
19101947

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

1913-
let _ = recording.cancel().await;
1950+
discard_recording(&app, recording).await?;
19141951

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

@@ -1927,41 +1964,11 @@ pub async fn delete_recording(app: AppHandle, state: MutableState<'_, App>) -> R
19271964
};
19281965

19291966
if let Some(recording) = recording_data {
1930-
let recording_dir = recording.recording_dir().clone();
19311967
CurrentRecordingChanged.emit(&app).ok();
19321968
RecordingStopped {}.emit(&app).ok();
19331969

1934-
let video_id = match &recording {
1935-
InProgressRecording::Instant {
1936-
video_upload_info,
1937-
segment_upload,
1938-
..
1939-
} => {
1940-
debug!(
1941-
"User deleted recording. Aborting multipart upload for {:?}",
1942-
video_upload_info.id
1943-
);
1944-
segment_upload.handle.abort();
1945-
1946-
Some(video_upload_info.id.clone())
1947-
}
1948-
_ => None,
1949-
};
1950-
1951-
let _ = recording.cancel().await;
1970+
let delete_result = discard_recording(&app, recording).await;
19521971

1953-
std::fs::remove_dir_all(&recording_dir).ok();
1954-
1955-
if let Some(id) = video_id {
1956-
let _ = app
1957-
.authed_api_request(
1958-
format!("/api/desktop/video/delete?videoId={id}"),
1959-
|c, url| c.delete(url),
1960-
)
1961-
.await;
1962-
}
1963-
1964-
// Check user's post-deletion behavior setting
19651972
let settings = GeneralSettingsStore::get(&app)
19661973
.ok()
19671974
.flatten()
@@ -1981,6 +1988,8 @@ pub async fn delete_recording(app: AppHandle, state: MutableState<'_, App>) -> R
19811988
.await;
19821989
}
19831990
}
1991+
1992+
delete_result?;
19841993
}
19851994

19861995
Ok(())

0 commit comments

Comments
 (0)