Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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...",
Expand Down
25 changes: 25 additions & 0 deletions opennow-stable/src/main/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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");
}
Expand Down Expand Up @@ -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;
}

Expand Down
2 changes: 2 additions & 0 deletions opennow-stable/src/renderer/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ export function App(): JSX.Element {
posterSizeScale: 1,
fps: 60,
maxBitrateMbps: 75,
recordingBitrateMbps: null,
streamClientMode: "web",
nativeStreamerBackend: "gstreamer",
nativeVideoBackend: "auto",
Expand Down Expand Up @@ -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={() => {
Expand Down
38 changes: 38 additions & 0 deletions opennow-stable/src/renderer/src/components/SettingsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2262,6 +2262,44 @@ export function SettingsPage({ settings, regions, onSettingChange, codecResults,
/>
</div>

<div className="settings-row settings-row--column">
<div className="settings-row-top">
<label className="settings-label">{t("settings.video.recordingBitrate")}</label>
<span className="settings-value-badge">
{settings.recordingBitrateMbps === null
? t("app.labels.auto")
: `${settings.recordingBitrateMbps} Mbps`}
</span>
</div>
<div className="settings-chip-row">
<button
type="button"
className={`settings-chip ${settings.recordingBitrateMbps === null ? "active" : ""}`}
onClick={() => handleChange("recordingBitrateMbps", null)}
>
<span>{t("app.labels.auto")}</span>
</button>
<button
type="button"
className={`settings-chip ${settings.recordingBitrateMbps !== null ? "active" : ""}`}
onClick={() => handleChange("recordingBitrateMbps", settings.recordingBitrateMbps ?? 75)}
>
<span>{t("settings.video.customBitrate")}</span>
</button>
</div>
<input
type="range"
className="settings-slider"
min={5}
max={200}
step={5}
value={settings.recordingBitrateMbps ?? 75}
disabled={settings.recordingBitrateMbps === null}
onChange={(e) => handleChange("recordingBitrateMbps", parseInt(e.target.value, 10))}
/>
<span className="settings-subtle-hint">{t("settings.video.recordingBitrateHint")}</span>
</div>

<div className="settings-row settings-row--column">
<div className="settings-row-top settings-row-top--compact">
<label className="settings-label settings-label--wrap">
Expand Down
13 changes: 11 additions & 2 deletions opennow-stable/src/renderer/src/components/StreamView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ interface StreamViewProps {
isFullscreen: boolean;
isConnecting: boolean;
gameTitle: string;
recordingBitrateMbps: number | null;
platformStore?: string;
onToggleFullscreen: () => void;
onConfirmExit: () => void;
Expand Down Expand Up @@ -575,6 +576,7 @@ export function StreamView({
isFullscreen,
isConnecting,
gameTitle,
recordingBitrateMbps,
platformStore,
onToggleFullscreen,
onConfirmExit,
Expand Down Expand Up @@ -1149,7 +1151,11 @@ export function StreamView({
}, 500);

let isFirstChunk = true;
const recorder = new MediaRecorder(composed, { mimeType });
const recorderOptions: MediaRecorderOptions = { mimeType };
if (recordingBitrateMbps !== null) {
recorderOptions.videoBitsPerSecond = Math.max(1, Math.min(200, Math.round(recordingBitrateMbps))) * 1_000_000;
}
const recorder = new MediaRecorder(composed, recorderOptions);

recorder.ondataavailable = (e: BlobEvent) => {
if (!e.data || e.data.size === 0) return;
Expand Down Expand Up @@ -1243,7 +1249,7 @@ export function StreamView({

mediaRecorderRef.current = recorder;
recorder.start(2000);
}, [gameTitle, isRecording, micTrack, recordingApiAvailable]);
}, [gameTitle, isRecording, micTrack, recordingApiAvailable, recordingBitrateMbps]);

// Cleanup: abort any active recording on unmount
useEffect(() => {
Expand Down Expand Up @@ -1734,6 +1740,9 @@ export function StreamView({
{usedMimeType && (
<span className="sidebar-hint sidebar-hint--codec">Codec: {usedMimeType}</span>
)}
<span className="sidebar-hint sidebar-hint--codec">
Recording bitrate: {recordingBitrateMbps === null ? "Auto" : `${recordingBitrateMbps} Mbps`}
</span>
<div className="sidebar-row sidebar-row--aligned">
<span className="sidebar-label">
{isRecording ? `Recording ${formatElapsed(Math.round(recordingDurationMs / 1000))}` : "Record"}
Expand Down
7 changes: 6 additions & 1 deletion opennow-stable/src/renderer/src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -1501,6 +1501,7 @@ body,
====================================================== */
.main-content {
flex: 1;
min-height: 0;
overflow: auto;
padding: 20px;
margin-top: var(--navbar-h);
Expand Down Expand Up @@ -3469,6 +3470,8 @@ button.game-card-store-chip.owned.active:hover {
====================================================== */
.settings-page {
max-width: 980px;
height: 100%;
min-height: 0;
margin: 0 auto;
display: flex;
flex-direction: column;
Expand Down Expand Up @@ -3747,7 +3750,7 @@ button.game-card-store-chip.owned.active:hover {
display: flex;
gap: 0;
min-height: 0;
flex: 1;
flex: 1 1 auto;
border: 1px solid var(--panel-border);
border-radius: var(--r-md);
overflow: hidden;
Expand Down Expand Up @@ -3864,7 +3867,9 @@ button.game-card-store-chip.owned.active:hover {
.settings-content {
flex: 1;
min-width: 0;
min-height: 0;
overflow-y: auto;
overscroll-behavior: contain;
padding: 16px 18px;
display: flex;
flex-direction: column;
Expand Down
2 changes: 2 additions & 0 deletions opennow-stable/src/shared/gfn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,8 @@ export interface Settings {
posterSizeScale: number;
fps: number;
maxBitrateMbps: number;
/** Recording video bitrate in Mbps; null means let MediaRecorder choose automatically */
recordingBitrateMbps: number | null;
streamClientMode: StreamClientMode;
nativeStreamerBackend: NativeStreamerBackendPreference;
nativeVideoBackend: NativeVideoBackendPreference;
Expand Down
Loading