feat(recording): add AudioOnly capture target and recording pipeline …#1881
feat(recording): add AudioOnly capture target and recording pipeline …#1881ManthanNimodiya wants to merge 4 commits into
Conversation
…dd Audio target variant
|
@greptileai please re-review |
| let raw_display_start = s | ||
| .pipeline | ||
| .screen | ||
| .as_ref() | ||
| .map(|sc| to_start_time(sc.first_timestamp)); | ||
| let display_start_time = raw_display_start.unwrap_or_else(|| { | ||
| mic_start_time | ||
| .or(camera_start_time) | ||
| .unwrap_or(s.start) | ||
| }); |
There was a problem hiding this comment.
Cross-track AV sync dropped for all screen recordings
The refactor that made screen optional accidentally removes the CROSS_TRACK_SNAP_SECS snapping that used to align display_start_time to camera or mic. When screen is Some (every non–audio-only recording), raw_display_start.unwrap_or_else(...) just returns the raw display timestamp and the snapping branch is never reached. Any screen+camera or screen+mic studio recording where the two clocks drift by more than CROSS_TRACK_SNAP_SECS will now produce an unsynchronised segment, whereas before it would have been corrected.
The fallback block should only fire when screen is None (audio-only). The snapping logic needs to be preserved for the Some case.
Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/recording/src/studio_recording.rs
Line: 963-972
Comment:
**Cross-track AV sync dropped for all screen recordings**
The refactor that made `screen` optional accidentally removes the `CROSS_TRACK_SNAP_SECS` snapping that used to align `display_start_time` to camera or mic. When `screen` is `Some` (every non–audio-only recording), `raw_display_start.unwrap_or_else(...)` just returns the raw display timestamp and the snapping branch is never reached. Any screen+camera or screen+mic studio recording where the two clocks drift by more than `CROSS_TRACK_SNAP_SECS` will now produce an unsynchronised segment, whereas before it would have been corrected.
The fallback block should only fire when `screen` is `None` (audio-only). The snapping logic needs to be preserved for the `Some` case.
How can I resolve this? If you propose a fix, please make it concise.|
@greptileai please re-review |
| for segment in &inner.segments { | ||
| let display_path = segment.display.path.to_path(recording_dir); | ||
| let display_path = segment.display.as_ref().map(|d| d.path.clone()).unwrap_or_default().to_path(recording_dir); | ||
| if display_path.is_dir() { | ||
| return true; | ||
| } |
There was a problem hiding this comment.
needs_fragment_remux always returns true for audio-only recordings
When display is None, unwrap_or_default() produces an empty RelativePathBuf, and .to_path(recording_dir) resolves to recording_dir itself. Since recording_dir.is_dir() is always true, every audio-only MultipleSegments recording is incorrectly flagged as fragmented. This causes the editor to open in a "finalizing" state for audio-only recordings and spawns finalize_studio_recording through a fragmented-remux code path that wasn't intended for them.
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src-tauri/src/recording.rs
Line: 3051-3055
Comment:
**`needs_fragment_remux` always returns `true` for audio-only recordings**
When `display` is `None`, `unwrap_or_default()` produces an empty `RelativePathBuf`, and `.to_path(recording_dir)` resolves to `recording_dir` itself. Since `recording_dir.is_dir()` is always `true`, every audio-only `MultipleSegments` recording is incorrectly flagged as fragmented. This causes the editor to open in a "finalizing" state for audio-only recordings and spawns `finalize_studio_recording` through a fragmented-remux code path that wasn't intended for them.
How can I resolve this? If you propose a fix, please make it concise.|
@greptileai please re-review |
Summary
What's not in this PR
(Phase 6-7)
Desktop UI (mode selector), desktop editor (waveform view), and share page (AudioPlayer fallback) are follow-up PRs that build on this foundation.
Test plan
cargo clippy --workspace --all-targets -- -D warningspasses cleanrecording-meta.jsonfiles withoutdisplayoraudio_onlydeserialize correctly via serde defaultsGreptile Summary
This PR adds the
AudioOnlyvariant toScreenCaptureTargetand wires it through the instant and studio recording pipelines, skipping screen/camera capture and building audio-only output. It also makesdisplay: Option<VideoMeta>in segment structs with serde defaults for backward compatibility, addsaudio_only: booltoRecordingMeta, and addsCurrentRecordingTarget::Audiofor the Tauri state layer.ScreenCaptureTarget::AudioOnlyis added and propagated through all match sites (telemetry, screenshot, capture pipeline, shareable content acquisition).displayis madeOption<VideoMeta>inSingleSegmentandMultipleSegmentswith backward-compatible serde defaults; all callers updated withas_ref().map(...).unwrap_or_default()fallbacks.Pipeline::screenis madeOption<OutputPipeline>in the studio pipeline, but the cancel-guard task inspawn_watcherimmediately cancels audio tracks whenscreenis absent.Confidence Score: 3/5
Not safe to merge without addressing the cancel-guard regression in the studio pipeline and the several audio-only finalization failures flagged across review rounds.
The cancel-guard task in
Pipeline::spawn_watcherimmediately cancels the microphone pipeline for every audio-only studio recording, making studio audio-only recordings non-functional. Combined with still-unresolved issues from earlier rounds —ProjectRecordingsMeta::newreturningErrfor audio-only (blocking finalization),audio_only: falsehardcoded inpersist_final_recording_meta, andAudioOnlyincorrectly opening the camera window — the audio-only path is not end-to-end functional.crates/recording/src/studio_recording.rs (cancel-guard in
spawn_watcher), crates/rendering/src/project_recordings.rs (SegmentRecordings.displaystill non-optional), apps/desktop/src-tauri/src/recording.rs (camera window triggered for AudioOnly,audio_only: falsehardcoded in meta writers)Important Files Changed
screenoptional throughout the studio pipeline to support audio-only mode; introduces a P1 bug where the cancel-guard task inspawn_watcherimmediately cancels mic/audio pipelines whenscreenisNone.Errreturns for audio-only, butSegmentRecordings.displayis stillVideo(non-optional), soProjectRecordingsMeta::newpropagated errors still prevent audio-only finalization.AudioOnlythrough recording start/finish/finalize paths;unwrap_or_default()on missing display paths is safe but firescreate_screenshotagainst the recording directory for audio-only.AudioOnlyvariant toScreenCaptureTargetwith correctNonereturns for display, area, rect, and name lookups.DashSegmentedAudioMuxer;video_infomade optional and handled correctly at stop time.displayoptional inSingleSegmentandMultipleSegmentwith backward-compatible serde defaults;min_fps/max_fpsupdated withunwrap_or(0)fallbacks.CurrentRecordingTarget::Audiovariant and mapsScreenCaptureTarget::AudioOnlyto it correctly.displayfield accesses to useOption; addsaudio_only: falseto all imported recording metas.screen_fpsanddisplay_start_offsetaccesses to useOption; falls back to 0 for audio-only.Comments Outside Diff (4)
crates/recording/src/studio_recording.rs, line 1615-1636 (link)audio_onlyflag always written asfalseby the studio pipelinepersist_final_recording_metahardcodesaudio_only: falsein theRecordingMetait writes to disk. For audio-only studio recordings,start_recordingcorrectly writesaudio_only: trueinitially, but this function overwrites the file at the end of the recording, so downstream consumers (editor, share page) will always readaudio_only: false. The same problem exists inwrite_in_progress_meta(line 1656), which runs before recording even begins and overwrites the initial value. Both functions need to either accept the capture target as a parameter or read and preserve the existingaudio_onlyvalue from disk.Prompt To Fix With AI
apps/desktop/src-tauri/src/recording.rs, line 853-869 (link)The
AudioOnlytarget enters the same branch asCameraOnlyhere, which callsShowCapWindow::Camera { centered: true }and setswas_camera_only_recording = true. For an audio-only recording there is no camera feed, so this opens a camera preview window with nothing to show and attaches incorrect state metadata. If the camera permission has not been granted, this could also produce an unexpected permission prompt. TheAudioOnlycase should likely skip this block entirely.Prompt To Fix With AI
apps/desktop/src-tauri/src/recording.rs, line 2749-2750 (link)SegmentRecordings.displayis a non-optionalVideo, soProjectRecordingsMeta::newreturnsErr("SingleSegment/MultipleSegment missing display")whenever display isNone. At both this call site (line 2749) andhandle_recording_finish(line 2506), the result is propagated with?. For audio-only studio recordings this means neitherconfig.writenor any downstream steps run — the recording's project config is never written and the recording appears incomplete from the editor's perspective.Prompt To Fix With AI
crates/recording/src/studio_recording.rs, line 595-609 (link)When
screenisNone(audio-only),screen_doneisNone, so theif let Some(done) = screen_doneguard is skipped and the spawned task proceeds directly to callingmic_cancel.cancel()(andcam_cancel,sys_cancel). Because the task is spawned but not immediately polled, it fires at the very next async yield point after recording starts — effectively cancelling the microphone pipeline before meaningful audio is captured. Every audio-only studio recording would produce an empty or near-empty output.The cancellation task should only be spawned when there is an actual screen pipeline to act as the trigger. When
screenis absent, the audio pipelines should run untilPipeline::stopis called explicitly.Prompt To Fix With AI
Prompt To Fix All With AI
Reviews (4): Last reviewed commit: "fix(recording): skip audio-only segments..." | Re-trigger Greptile