Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
9c743e2
cleanup `upload_video` arguments
oscartbeaumont Sep 27, 2025
a0d7028
wip
oscartbeaumont Sep 27, 2025
8a1d081
wip
oscartbeaumont Sep 28, 2025
9d14a0d
make `progress_upload` required for instant mode
oscartbeaumont Sep 28, 2025
ea2bd20
fix uploading of thumbnails
oscartbeaumont Sep 28, 2025
313d7f1
absolute mess
oscartbeaumont Sep 28, 2025
c813c07
store recordings into project meta?
oscartbeaumont Sep 28, 2025
a65d871
cursed realtime upload progress
oscartbeaumont Sep 28, 2025
7a8bf58
break out API definitions + use tracing for logging in `upload.rs`
oscartbeaumont Sep 28, 2025
ead7d0f
working stream-based uploader
oscartbeaumont Sep 28, 2025
b288365
bring back progress tracking to upload
oscartbeaumont Sep 28, 2025
7a4d428
implement `from_file` abstraction
oscartbeaumont Sep 29, 2025
ecddfb7
abstract another endpoint into `api` module
oscartbeaumont Sep 29, 2025
c5c8e84
abstract more API + retry on S3 upload request
oscartbeaumont Sep 29, 2025
476a649
finish overhauling upload code
oscartbeaumont Sep 29, 2025
c411b02
restructure project file again
oscartbeaumont Sep 29, 2025
cc1d0de
UI for uploading state + errors
oscartbeaumont Sep 29, 2025
8356ec0
show recording and pending status
oscartbeaumont Sep 29, 2025
2da924c
merge in changes from #1077
oscartbeaumont Sep 29, 2025
2806aff
Merge branch 'main' into local-upload-tracking
oscartbeaumont Sep 29, 2025
19c495c
upload progress working in sync pogggg
oscartbeaumont Sep 29, 2025
49b9dc5
wip
oscartbeaumont Sep 29, 2025
c48c57d
polish off todo's in `recordings.tsx`
oscartbeaumont Sep 29, 2025
a101f6d
a bunch of random fixes
oscartbeaumont Sep 29, 2025
7ea039c
fixes
oscartbeaumont Oct 2, 2025
d890931
fix
oscartbeaumont Oct 2, 2025
50aae68
Merge branch 'main' into local-upload-tracking
oscartbeaumont Oct 2, 2025
cc85380
resumable system
oscartbeaumont Oct 2, 2025
1bb11a9
format
oscartbeaumont Oct 2, 2025
9c4f064
feature flag the new uploader
oscartbeaumont Oct 2, 2025
529cc5c
fixes to some remaining issues
oscartbeaumont Oct 2, 2025
b7c38c7
fix button visibility on `CapCard`
oscartbeaumont Oct 2, 2025
a782308
wip
oscartbeaumont Oct 2, 2025
6bccc9d
Clippy improvements
oscartbeaumont Oct 2, 2025
1f88951
cleanup
oscartbeaumont Oct 2, 2025
02edbdf
fix CI
oscartbeaumont Oct 2, 2025
a5e332e
fix Typescript
oscartbeaumont Oct 2, 2025
d4b9b0c
fixes
oscartbeaumont Oct 2, 2025
35ebc8d
make `CapCard` more conservative with upload status
oscartbeaumont Oct 2, 2025
fa894b7
Merge branch 'main' into local-upload-tracking
oscartbeaumont Oct 2, 2025
f05f2e4
CodeRabbit fixes
oscartbeaumont Oct 2, 2025
df98f25
fix some `CapCard` states
oscartbeaumont Oct 2, 2025
2cae777
fix retry policy
oscartbeaumont Oct 2, 2025
46b9f16
wip
oscartbeaumont Oct 2, 2025
f26d5c5
Merge branch 'main' into local-upload-tracking
oscartbeaumont Oct 3, 2025
b5b7e39
Merge branch 'main' into local-upload-tracking
oscartbeaumont Oct 6, 2025
ef47a9d
fixes
oscartbeaumont Oct 6, 2025
8d6cb13
emit first chunk multiple times
oscartbeaumont Oct 6, 2025
f0435a4
potentially fix the missing video toolbar sometimes
oscartbeaumont Oct 6, 2025
58dede1
improve chunk emit logic
oscartbeaumont Oct 6, 2025
30d4726
fix retry policy for s3 upload
oscartbeaumont Oct 6, 2025
13c71cf
stttttrrrriiiinnnnggg
oscartbeaumont Oct 6, 2025
a1b8290
Merge branch 'main' into local-upload-tracking
oscartbeaumont Oct 6, 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
2 changes: 2 additions & 0 deletions Cargo.lock

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

2 changes: 2 additions & 0 deletions apps/desktop/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ wgpu.workspace = true
bytemuck = "1.23.1"
kameo = "0.17.2"
tauri-plugin-sentry = "0.5.0"
thiserror.workspace = true
bytes = "1.10.1"

[target.'cfg(target_os = "macos")'.dependencies]
core-graphics = "0.24.0"
Expand Down
26 changes: 20 additions & 6 deletions apps/desktop/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -910,6 +910,7 @@ async fn get_video_metadata(path: PathBuf) -> Result<VideoRecordingMetadata, Str
.map(|s| recording_meta.path(&s.display.path))
.collect(),
},
RecordingMetaInner::InProgress { .. } => todo!(),
};

let duration = display_paths
Expand Down Expand Up @@ -1111,9 +1112,9 @@ async fn upload_exported_video(
&app,
upload_id.clone(),
output_path,
Some(s3_config),
Some(meta.project_path.join("screenshots/display.jpg")),
Some(metadata),
meta.project_path.join("screenshots/display.jpg"),
s3_config,
metadata,
Some(channel.clone()),
)
.await
Expand Down Expand Up @@ -1389,19 +1390,31 @@ async fn save_file_dialog(
}
}

// #[derive(Serialize, specta::Type)]
// #[serde(tag = "status")]
// pub enum RecordingStatus {
// Recording,
// Failed { error: String },
// Complete { mode: RecordingMode },
// }
//
// #[serde(flatten)]
// pub status: RecordingStatus,

#[derive(Serialize, specta::Type)]
pub struct RecordingMetaWithMode {
#[serde(flatten)]
pub inner: RecordingMeta,
pub mode: RecordingMode,
pub mode: Option<RecordingMode>,
}

impl RecordingMetaWithMode {
fn new(inner: RecordingMeta) -> Self {
Self {
mode: match &inner.inner {
RecordingMetaInner::Studio(_) => RecordingMode::Studio,
RecordingMetaInner::Instant(_) => RecordingMode::Instant,
RecordingMetaInner::Studio(_) => Some(RecordingMode::Studio),
RecordingMetaInner::Instant(_) => Some(RecordingMode::Instant),
RecordingMetaInner::InProgress { .. } => None,
},
inner,
}
Expand Down Expand Up @@ -2489,6 +2502,7 @@ fn open_project_from_path(path: &Path, app: AppHandle) -> Result<(), String> {
}
}
}
RecordingMetaInner::InProgress { recording } => todo!(),
}

Ok(())
Expand Down
161 changes: 85 additions & 76 deletions apps/desktop/src-tauri/src/recording.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ use crate::{
open_external_link,
presets::PresetsStore,
upload::{
InstantMultipartUpload, build_video_meta, create_or_get_video, prepare_screenshot_upload,
upload_video,
InstantMultipartUpload, PresignedS3PutRequest, PresignedS3PutRequestMethod,
build_video_meta, bytes_into_stream, compress_image, create_or_get_video,
do_presigned_upload, upload_video,
},
web_api::ManagerExt,
windows::{CapWindowId, ShowCapWindow},
Expand All @@ -42,7 +43,7 @@ pub enum InProgressRecording {
Instant {
target_name: String,
handle: instant_recording::ActorHandle,
progressive_upload: Option<InstantMultipartUpload>,
progressive_upload: InstantMultipartUpload,
video_upload_info: VideoUploadInfo,
inputs: StartRecordingInputs,
recording_dir: PathBuf,
Expand Down Expand Up @@ -135,7 +136,7 @@ pub enum CompletedRecording {
Instant {
recording: instant_recording::CompletedRecording,
target_name: String,
progressive_upload: Option<InstantMultipartUpload>,
progressive_upload: InstantMultipartUpload,
video_upload_info: VideoUploadInfo,
},
Studio {
Expand Down Expand Up @@ -284,6 +285,24 @@ pub async fn start_recording(
RecordingMode::Studio => None,
};

let date_time = if cfg!(windows) {
// Windows doesn't support colon in file paths
chrono::Local::now().format("%Y-%m-%d %H.%M.%S")
} else {
chrono::Local::now().format("%Y-%m-%d %H:%M:%S")
};

let meta = RecordingMeta {
platform: Some(Platform::default()),
project_path: recording_dir.clone(),
sharing: None, // TODO: Is this gonna be problematic as it was previously always set
pretty_name: format!("{target_name} {date_time}"),
inner: RecordingMetaInner::InProgress { recording: true },
};

meta.save_for_project()
.map_err(|e| format!("Failed to save recording meta: {e}"))?;

match &inputs.capture_target {
ScreenCaptureTarget::Window { id: _id } => {
if let Some(show) = inputs
Expand Down Expand Up @@ -342,22 +361,11 @@ pub async fn start_recording(
}

let (finish_upload_tx, finish_upload_rx) = flume::bounded(1);
let progressive_upload = video_upload_info
.as_ref()
.filter(|_| matches!(inputs.mode, RecordingMode::Instant))
.map(|video_upload_info| {
InstantMultipartUpload::spawn(
app.clone(),
id.clone(),
recording_dir.join("content/output.mp4"),
video_upload_info.clone(),
Some(finish_upload_rx),
)
});

debug!("spawning start_recording actor");

// done in spawn to catch panics just in case
let app_handle = app.clone();
let spawn_actor_res = async {
spawn_actor({
let state_mtx = Arc::clone(&state_mtx);
Expand Down Expand Up @@ -420,6 +428,14 @@ pub async fn start_recording(
return Err("Video upload info not found".to_string());
};

let progressive_upload = InstantMultipartUpload::spawn(
app_handle,
id.clone(),
recording_dir.join("content/output.mp4"),
video_upload_info.clone(),
Some(finish_upload_rx),
);

let mut builder = instant_recording::Actor::builder(
recording_dir.clone(),
inputs.capture_target.clone(),
Expand Down Expand Up @@ -760,57 +776,62 @@ async fn handle_recording_finish(
let video_upload_info = video_upload_info.clone();

async move {
if let Some(progressive_upload) = progressive_upload {
let video_upload_succeeded = match progressive_upload
.handle
.await
.map_err(|e| e.to_string())
.and_then(|r| r)
{
Ok(()) => {
info!(
"Not attempting instant recording upload as progressive upload succeeded"
);
true
}
Err(e) => {
error!("Progressive upload failed: {}", e);
false
}
};

let _ = screenshot_task.await;

if video_upload_succeeded {
let resp = prepare_screenshot_upload(
&app,
&video_upload_info.config.clone(),
display_screenshot,
)
.await;

match resp {
Ok(r)
if r.status().as_u16() >= 200 && r.status().as_u16() < 300 =>
{
info!("Screenshot uploaded successfully");
}
Ok(r) => {
error!("Failed to upload screenshot: {}", r.status());
}
Err(e) => {
error!("Failed to upload screenshot: {e}");
}
let video_upload_succeeded = match progressive_upload
.handle
.await
.map_err(|e| e.to_string())
.and_then(|r| r)
{
Ok(()) => {
info!(
"Not attempting instant recording upload as progressive upload succeeded"
);
true
}
Err(e) => {
error!("Progressive upload failed: {}", e);
false
}
};

let _ = screenshot_task.await;

if video_upload_succeeded {
if let Ok(result) =
compress_image(display_screenshot).await
.map_err(|err|
error!("Error compressing thumbnail for instant mode progressive upload: {err}")
) {
let (stream, total_size) = bytes_into_stream(result);
do_presigned_upload(
&app,
stream,
total_size,
crate::upload::PresignedS3PutRequest {
video_id: video_upload_info.id.clone(),
subpath: "screenshot/screen-capture.jpg".to_string(),
method: PresignedS3PutRequestMethod::Put,
meta: None,
},
|p| {} // TODO: Progress reporting
)
.await
.map_err(|err| {
error!("Error updating thumbnail for instant mode progressive upload: {err}")
})
.ok();
}
} else {
let meta = build_video_meta(&output_path).ok();
} else {
if let Ok(meta) = build_video_meta(&output_path)
.map_err(|err| error!("Error getting video metdata: {}", err))
{
// The upload_video function handles screenshot upload, so we can pass it along
match upload_video(
&app,
video_upload_info.id.clone(),
output_path,
Some(video_upload_info.config.clone()),
Some(display_screenshot.clone()),
display_screenshot.clone(),
video_upload_info.config.clone(),
meta,
None,
)
Expand Down Expand Up @@ -840,21 +861,9 @@ async fn handle_recording_finish(
}
};

let date_time = if cfg!(windows) {
// Windows doesn't support colon in file paths
chrono::Local::now().format("%Y-%m-%d %H.%M.%S")
} else {
chrono::Local::now().format("%Y-%m-%d %H:%M:%S")
};

let meta = RecordingMeta {
platform: Some(Platform::default()),
project_path: recording_dir.clone(),
sharing,
pretty_name: format!("{target_name} {date_time}"),
inner: meta_inner,
};

// TODO: Can we avoid reloading it from disk by parsing as arg?
let mut meta = RecordingMeta::load_for_project(&recording_dir).unwrap();
meta.inner = meta_inner;
meta.save_for_project()
.map_err(|e| format!("Failed to save recording meta: {e}"))?;

Expand Down
Loading
Loading