diff --git a/locales/en.json b/locales/en.json
index 4394639a5..1a5a17c8f 100644
--- a/locales/en.json
+++ b/locales/en.json
@@ -403,6 +403,9 @@
"encoder": "Encoder",
"colorDepth": "Color Depth",
"maxBitrate": "Max Bitrate",
+ "recordingBitrate": "Recording Bitrate",
+ "recordingBitrateHint": "Controls local recording video bitrate. Auto lets the browser choose.",
+ "customBitrate": "Custom",
"appliesAfterRestart": "Applies after app restart.",
"requiresH265OrAv1": "This mode requires H265 or AV1. Codec will be auto-switched.",
"testing": "Testing...",
diff --git a/opennow-stable/src/main/settings.ts b/opennow-stable/src/main/settings.ts
index 04e081b6c..f62c92fb1 100644
--- a/opennow-stable/src/main/settings.ts
+++ b/opennow-stable/src/main/settings.ts
@@ -34,6 +34,8 @@ export interface Settings {
fps: number;
/** Maximum bitrate in Mbps (cap at 150) */
maxBitrateMbps: number;
+ /** Recording video bitrate in Mbps (null = MediaRecorder auto, cap at 200) */
+ recordingBitrateMbps: number | null;
/** Stream client implementation to use for new sessions */
streamClientMode: StreamClientMode;
/** Native streamer backend preference for new native sessions */
@@ -155,12 +157,24 @@ function normalizeAppAccentColor(raw: unknown): AppAccentColor {
return APP_ACCENT_COLORS.has(raw as AppAccentColor) ? (raw as AppAccentColor) : "green";
}
+function normalizeRecordingBitrateMbps(raw: unknown): number | null {
+ if (raw === null || raw === undefined) {
+ return null;
+ }
+ const value = Number(raw);
+ if (!Number.isFinite(value)) {
+ return null;
+ }
+ return Math.max(1, Math.min(200, Math.round(value)));
+}
+
const DEFAULT_SETTINGS: Settings = {
resolution: "1920x1080",
aspectRatio: "16:9",
posterSizeScale: 1,
fps: 60,
maxBitrateMbps: 75,
+ recordingBitrateMbps: null,
streamClientMode: "web",
nativeStreamerBackend: "gstreamer",
nativeVideoBackend: "auto",
@@ -271,6 +285,11 @@ export class SettingsManager {
}
merged.mouseAcceleration = Math.max(1, Math.min(150, Math.round(merged.mouseAcceleration)));
+ const recordingBitrateBefore = merged.recordingBitrateMbps;
+ merged.recordingBitrateMbps = normalizeRecordingBitrateMbps(merged.recordingBitrateMbps);
+ if (merged.recordingBitrateMbps !== recordingBitrateBefore) {
+ migrated = true;
+ }
if (migrated) {
writeFileSync(this.settingsPath, JSON.stringify(merged, null, 2), "utf-8");
}
@@ -321,6 +340,12 @@ export class SettingsManager {
migrated = true;
}
+ const recordingBitrate = normalizeRecordingBitrateMbps(settings.recordingBitrateMbps);
+ if (settings.recordingBitrateMbps !== recordingBitrate) {
+ settings.recordingBitrateMbps = recordingBitrate;
+ migrated = true;
+ }
+
return migrated;
}
diff --git a/opennow-stable/src/renderer/src/App.tsx b/opennow-stable/src/renderer/src/App.tsx
index 239576833..407988b23 100644
--- a/opennow-stable/src/renderer/src/App.tsx
+++ b/opennow-stable/src/renderer/src/App.tsx
@@ -243,6 +243,7 @@ export function App(): JSX.Element {
posterSizeScale: 1,
fps: 60,
maxBitrateMbps: 75,
+ recordingBitrateMbps: null,
streamClientMode: "web",
nativeStreamerBackend: "gstreamer",
nativeVideoBackend: "auto",
@@ -3479,6 +3480,7 @@ export function App(): JSX.Element {
isFullscreen={sessionFullscreen || !!document.fullscreenElement}
isConnecting={streamStatus === "connecting"}
isStreaming={isStreaming}
+ recordingBitrateMbps={settings.recordingBitrateMbps}
gameTitle={streamingGame?.title ?? t("app.labels.game")}
platformStore={streamingStore ?? undefined}
onToggleFullscreen={() => {
diff --git a/opennow-stable/src/renderer/src/components/SettingsPage.tsx b/opennow-stable/src/renderer/src/components/SettingsPage.tsx
index 9247ed0dd..7f71ee7a7 100644
--- a/opennow-stable/src/renderer/src/components/SettingsPage.tsx
+++ b/opennow-stable/src/renderer/src/components/SettingsPage.tsx
@@ -2262,6 +2262,44 @@ export function SettingsPage({ settings, regions, onSettingChange, codecResults,
/>
+
+
+
+
+ {settings.recordingBitrateMbps === null
+ ? t("app.labels.auto")
+ : `${settings.recordingBitrateMbps} Mbps`}
+
+
+
+
+
+
+
handleChange("recordingBitrateMbps", parseInt(e.target.value, 10))}
+ />
+
{t("settings.video.recordingBitrateHint")}
+
+