Skip to content

Commit 466ab59

Browse files
feat: Implement resolution picker setting for Instant Mode (#1258)
* feat: Implement resolution picker for Instant Mode * Add output size support to H264 encoder * CodeRabbit suggestions * CodeRabbit suggestion * build fix * Coderabbit suggestions * Coderabbit suggestions * Coderabbit suggestions * format * Coderabbit suggestion * treat as 'max resolution' rather than fixed * account for ultrawide/ultratall capture in resolution restriction * fix settings * clamp resolution better * cleanup * format --------- Co-authored-by: Brendan Allan <brendonovich@outlook.com>
1 parent f60cd3a commit 466ab59

File tree

8 files changed

+413
-83
lines changed

8 files changed

+413
-83
lines changed

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,8 @@ pub struct GeneralSettingsStore {
120120
pub excluded_windows: Vec<WindowExclusion>,
121121
#[serde(default)]
122122
pub delete_instant_recordings_after_upload: bool,
123+
#[serde(default = "default_instant_mode_max_resolution")]
124+
pub instant_mode_max_resolution: u32,
123125
}
124126

125127
fn default_enable_native_camera_preview() -> bool {
@@ -139,6 +141,10 @@ fn default_true() -> bool {
139141
true
140142
}
141143

144+
fn default_instant_mode_max_resolution() -> u32 {
145+
1920
146+
}
147+
142148
fn default_server_url() -> String {
143149
std::option_env!("VITE_SERVER_URL")
144150
.unwrap_or("https://cap.so")
@@ -180,6 +186,7 @@ impl Default for GeneralSettingsStore {
180186
post_deletion_behaviour: PostDeletionBehaviour::DoNothing,
181187
excluded_windows: default_excluded_windows(),
182188
delete_instant_recordings_after_upload: false,
189+
instant_mode_max_resolution: 1920,
183190
}
184191
}
185192
}

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,7 @@ pub async fn start_recording(
466466
Err(SendError::HandlerError(camera::LockFeedError::NoInput)) => None,
467467
Err(e) => return Err(e.to_string()),
468468
};
469+
469470
#[cfg(target_os = "macos")]
470471
let shareable_content = crate::platform::get_shareable_content()
471472
.await
@@ -537,7 +538,13 @@ pub async fn start_recording(
537538
recording_dir.clone(),
538539
inputs.capture_target.clone(),
539540
)
540-
.with_system_audio(inputs.capture_system_audio);
541+
.with_system_audio(inputs.capture_system_audio)
542+
.with_max_output_size(
543+
general_settings
544+
.as_ref()
545+
.map(|settings| settings.instant_mode_max_resolution)
546+
.unwrap_or_else(|| 1920),
547+
);
541548

542549
#[cfg(target_os = "macos")]
543550
{

apps/desktop/src/routes/(window-chrome)/settings/general.tsx

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,9 @@ const getWindowOptionLabel = (window: CaptureWindow) => {
6161
return parts.join(" • ");
6262
};
6363

64-
const createDefaultGeneralSettings = (): GeneralSettingsStore => ({
64+
type ExtendedGeneralSettingsStore = GeneralSettingsStore;
65+
66+
const createDefaultGeneralSettings = (): ExtendedGeneralSettingsStore => ({
6567
uploadIndividualFiles: false,
6668
hideDockIcon: false,
6769
autoCreateShareableLink: false,
@@ -71,11 +73,12 @@ const createDefaultGeneralSettings = (): GeneralSettingsStore => ({
7173
autoZoomOnClicks: false,
7274
custom_cursor_capture2: true,
7375
excludedWindows: [],
76+
instantModeMaxResolution: 1920,
7477
});
7578

7679
const deriveInitialSettings = (
7780
store: GeneralSettingsStore | null,
78-
): GeneralSettingsStore => {
81+
): ExtendedGeneralSettingsStore => {
7982
const defaults = createDefaultGeneralSettings();
8083
if (!store) return defaults;
8184

@@ -85,6 +88,16 @@ const deriveInitialSettings = (
8588
};
8689
};
8790

91+
const INSTANT_MODE_RESOLUTION_OPTIONS = [
92+
{ value: 1280, label: "720p" },
93+
{ value: 1920, label: "1080p" },
94+
{ value: 2560, label: "1440p" },
95+
{ value: 3840, label: "4K" },
96+
] satisfies {
97+
value: number;
98+
label: string;
99+
}[];
100+
88101
export default function GeneralSettings() {
89102
const [store] = createResource(() => generalSettingsStore.get());
90103

@@ -167,7 +180,7 @@ function AppearanceSection(props: {
167180
}
168181

169182
function Inner(props: { initialStore: GeneralSettingsStore | null }) {
170-
const [settings, setSettings] = createStore<GeneralSettingsStore>(
183+
const [settings, setSettings] = createStore<ExtendedGeneralSettingsStore>(
171184
deriveInitialSettings(props.initialStore),
172185
);
173186

@@ -417,13 +430,23 @@ function Inner(props: { initialStore: GeneralSettingsStore | null }) {
417430
)}
418431

419432
<SettingGroup title="Recording">
433+
<SelectSettingItem
434+
label="Instant mode max resolution"
435+
description="Choose the maximum resolution for Instant Mode recordings."
436+
value={settings.instantModeMaxResolution ?? 1920}
437+
onChange={(value) =>
438+
handleChange("instantModeMaxResolution", value)
439+
}
440+
options={INSTANT_MODE_RESOLUTION_OPTIONS.map((option) => ({
441+
text: option.label,
442+
value: option.value,
443+
}))}
444+
/>
420445
<SelectSettingItem
421446
label="Recording countdown"
422447
description="Countdown before recording starts"
423448
value={settings.recordingCountdown ?? 0}
424-
onChange={(value) =>
425-
handleChange("recordingCountdown", value as number)
426-
}
449+
onChange={(value) => handleChange("recordingCountdown", value)}
427450
options={[
428451
{ text: "Off", value: 0 },
429452
{ text: "3 seconds", value: 3 },
@@ -436,10 +459,7 @@ function Inner(props: { initialStore: GeneralSettingsStore | null }) {
436459
description="The main window recording start behaviour"
437460
value={settings.mainWindowRecordingStartBehaviour ?? "close"}
438461
onChange={(value) =>
439-
handleChange(
440-
"mainWindowRecordingStartBehaviour",
441-
value as MainWindowRecordingStartBehaviour,
442-
)
462+
handleChange("mainWindowRecordingStartBehaviour", value)
443463
}
444464
options={[
445465
{ text: "Close", value: "close" },
@@ -451,10 +471,7 @@ function Inner(props: { initialStore: GeneralSettingsStore | null }) {
451471
description="The studio recording finish behaviour"
452472
value={settings.postStudioRecordingBehaviour ?? "openEditor"}
453473
onChange={(value) =>
454-
handleChange(
455-
"postStudioRecordingBehaviour",
456-
value as PostStudioRecordingBehaviour,
457-
)
474+
handleChange("postStudioRecordingBehaviour", value)
458475
}
459476
options={[
460477
{ text: "Open editor", value: "openEditor" },
@@ -468,12 +485,7 @@ function Inner(props: { initialStore: GeneralSettingsStore | null }) {
468485
label="After deleting recording behaviour"
469486
description="Should Cap reopen after deleting an in progress recording?"
470487
value={settings.postDeletionBehaviour ?? "doNothing"}
471-
onChange={(value) =>
472-
handleChange(
473-
"postDeletionBehaviour",
474-
value as PostDeletionBehaviour,
475-
)
476-
}
488+
onChange={(value) => handleChange("postDeletionBehaviour", value)}
477489
options={[
478490
{ text: "Do Nothing", value: "doNothing" },
479491
{

apps/desktop/src/utils/tauri.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,7 @@ export type ExportSettings = ({ format: "Mp4" } & Mp4ExportSettings) | ({ format
393393
export type FileType = "recording" | "screenshot"
394394
export type Flags = { captions: boolean }
395395
export type FramesRendered = { renderedCount: number; totalFrames: number; type: "FramesRendered" }
396-
export type GeneralSettingsStore = { instanceId?: string; uploadIndividualFiles?: boolean; hideDockIcon?: boolean; hapticsEnabled?: boolean; autoCreateShareableLink?: boolean; enableNotifications?: boolean; disableAutoOpenLinks?: boolean; hasCompletedStartup?: boolean; theme?: AppTheme; commercialLicense?: CommercialLicense | null; lastVersion?: string | null; windowTransparency?: boolean; postStudioRecordingBehaviour?: PostStudioRecordingBehaviour; mainWindowRecordingStartBehaviour?: MainWindowRecordingStartBehaviour; custom_cursor_capture2?: boolean; serverUrl?: string; recordingCountdown?: number | null; enableNativeCameraPreview: boolean; autoZoomOnClicks?: boolean; enableNewRecordingFlow: boolean; postDeletionBehaviour?: PostDeletionBehaviour; excludedWindows?: WindowExclusion[]; deleteInstantRecordingsAfterUpload?: boolean }
396+
export type GeneralSettingsStore = { instanceId?: string; uploadIndividualFiles?: boolean; hideDockIcon?: boolean; hapticsEnabled?: boolean; autoCreateShareableLink?: boolean; enableNotifications?: boolean; disableAutoOpenLinks?: boolean; hasCompletedStartup?: boolean; theme?: AppTheme; commercialLicense?: CommercialLicense | null; lastVersion?: string | null; windowTransparency?: boolean; postStudioRecordingBehaviour?: PostStudioRecordingBehaviour; mainWindowRecordingStartBehaviour?: MainWindowRecordingStartBehaviour; custom_cursor_capture2?: boolean; serverUrl?: string; recordingCountdown?: number | null; enableNativeCameraPreview: boolean; autoZoomOnClicks?: boolean; enableNewRecordingFlow: boolean; postDeletionBehaviour?: PostDeletionBehaviour; excludedWindows?: WindowExclusion[]; deleteInstantRecordingsAfterUpload?: boolean; instantModeMaxResolution?: number }
397397
export type GifExportSettings = { fps: number; resolution_base: XY<number>; quality: GifQuality | null }
398398
export type GifQuality = {
399399
/**

0 commit comments

Comments
 (0)