remember stream resolution & fps settings

This commit is contained in:
Vendicated 2025-02-06 04:51:20 +01:00
parent 9b0503f49d
commit b620e07445
No known key found for this signature in database
GPG key ID: D66986BAF75ECF18
4 changed files with 69 additions and 27 deletions

View file

@ -22,7 +22,7 @@ import {
import { Node } from "@vencord/venmic"; import { Node } from "@vencord/venmic";
import type { Dispatch, SetStateAction } from "react"; import type { Dispatch, SetStateAction } from "react";
import { addPatch } from "renderer/patches/shared"; import { addPatch } from "renderer/patches/shared";
import { useSettings } from "renderer/settings"; import { State, useSettings, useVesktopState } from "renderer/settings";
import { classNameFactory, isLinux, isWindows } from "renderer/utils"; import { classNameFactory, isLinux, isWindows } from "renderer/utils";
const StreamResolutions = ["480", "720", "1080", "1440", "2160"] as const; const StreamResolutions = ["480", "720", "1080", "1440", "2160"] as const;
@ -46,8 +46,6 @@ interface AudioItem {
} }
interface StreamSettings { interface StreamSettings {
resolution: StreamResolution;
fps: StreamFps;
audio: boolean; audio: boolean;
contentHint?: string; contentHint?: string;
includeSources?: AudioSources; includeSources?: AudioSources;
@ -79,10 +77,11 @@ addPatch({
} }
], ],
patchStreamQuality(opts: any) { patchStreamQuality(opts: any) {
if (!currentSettings) return; const { screenshareQuality } = State.store;
if (!screenshareQuality) return;
const framerate = Number(currentSettings.fps); const framerate = Number(screenshareQuality.frameRate);
const height = Number(currentSettings.resolution); const height = Number(screenshareQuality.resolution);
const width = Math.round(height * (16 / 9)); const width = Math.round(height * (16 / 9));
Object.assign(opts, { Object.assign(opts, {
@ -316,14 +315,14 @@ function AudioSettingsModal({
); );
} }
function OptionRadio(props: { function OptionRadio<Settings extends object, Key extends keyof Settings>(props: {
options: Array<string> | ReadonlyArray<string>; options: Array<string> | ReadonlyArray<string>;
labels?: Array<string>; labels?: Array<string>;
settingsKey: string; settings: Settings;
settings: StreamSettings; settingsKey: Key;
setSettings: Dispatch<SetStateAction<StreamSettings>>; onChange: (option: string) => void;
}) { }) {
const { options, setSettings, settings, settingsKey, labels } = props; const { options, settings, settingsKey, labels, onChange } = props;
return ( return (
<div className={cl("option-radios")}> <div className={cl("option-radios")}>
@ -336,7 +335,7 @@ function OptionRadio(props: {
name="fps" name="fps"
value={option} value={option}
checked={settings[settingsKey] === option} checked={settings[settingsKey] === option}
onChange={() => setSettings(s => ({ ...s, [settingsKey]: option }))} onChange={() => onChange(option)}
/> />
</label> </label>
))} ))}
@ -344,7 +343,7 @@ function OptionRadio(props: {
); );
} }
function StreamSettings({ function StreamSettingsUi({
source, source,
settings, settings,
setSettings, setSettings,
@ -356,6 +355,7 @@ function StreamSettings({
skipPicker: boolean; skipPicker: boolean;
}) { }) {
const Settings = useSettings(); const Settings = useSettings();
const qualitySettings = State.store.screenshareQuality!;
const [thumb] = useAwaiter( const [thumb] = useAwaiter(
() => (skipPicker ? Promise.resolve(source.url) : VesktopNative.capturer.getLargeThumbnail(source.id)), () => (skipPicker ? Promise.resolve(source.url) : VesktopNative.capturer.getLargeThumbnail(source.id)),
@ -393,9 +393,9 @@ function StreamSettings({
<Forms.FormTitle>Resolution</Forms.FormTitle> <Forms.FormTitle>Resolution</Forms.FormTitle>
<OptionRadio <OptionRadio
options={StreamResolutions} options={StreamResolutions}
settings={qualitySettings}
settingsKey="resolution" settingsKey="resolution"
settings={settings} onChange={value => (qualitySettings.resolution = value)}
setSettings={setSettings}
/> />
</section> </section>
@ -403,9 +403,9 @@ function StreamSettings({
<Forms.FormTitle>Frame Rate</Forms.FormTitle> <Forms.FormTitle>Frame Rate</Forms.FormTitle>
<OptionRadio <OptionRadio
options={StreamFps} options={StreamFps}
settingsKey="fps" settings={qualitySettings}
settings={settings} settingsKey="frameRate"
setSettings={setSettings} onChange={value => (qualitySettings.frameRate = value)}
/> />
</section> </section>
</div> </div>
@ -416,9 +416,9 @@ function StreamSettings({
<OptionRadio <OptionRadio
options={["motion", "detail"]} options={["motion", "detail"]}
labels={["Prefer Smoothness", "Prefer Clarity"]} labels={["Prefer Smoothness", "Prefer Clarity"]}
settingsKey="contentHint"
settings={settings} settings={settings}
setSettings={setSettings} settingsKey="contentHint"
onChange={option => setSettings(s => ({ ...s, contentHint: option }))}
/> />
<div className={cl("hint-description")}> <div className={cl("hint-description")}>
<p> <p>
@ -696,12 +696,14 @@ function ModalComponent({
}) { }) {
const [selected, setSelected] = useState<string | undefined>(skipPicker ? screens[0].id : void 0); const [selected, setSelected] = useState<string | undefined>(skipPicker ? screens[0].id : void 0);
const [settings, setSettings] = useState<StreamSettings>({ const [settings, setSettings] = useState<StreamSettings>({
resolution: "720",
fps: "30",
contentHint: "motion", contentHint: "motion",
audio: true, audio: true,
includeSources: "None" includeSources: "None"
}); });
const qualitySettings = (useVesktopState().screenshareQuality ??= {
resolution: "720",
frameRate: "30"
});
return ( return (
<Modals.ModalRoot {...modalProps} size={ModalSize.MEDIUM}> <Modals.ModalRoot {...modalProps} size={ModalSize.MEDIUM}>
@ -713,7 +715,7 @@ function ModalComponent({
{!selected ? ( {!selected ? (
<ScreenPicker screens={screens} chooseScreen={setSelected} /> <ScreenPicker screens={screens} chooseScreen={setSelected} />
) : ( ) : (
<StreamSettings <StreamSettingsUi
source={screens.find(s => s.id === selected)!} source={screens.find(s => s.id === selected)!}
settings={settings} settings={settings}
setSettings={setSettings} setSettings={setSettings}
@ -727,8 +729,8 @@ function ModalComponent({
onClick={() => { onClick={() => {
currentSettings = settings; currentSettings = settings;
try { try {
const frameRate = Number(settings.fps); const frameRate = Number(qualitySettings.frameRate);
const height = Number(settings.resolution); const height = Number(qualitySettings.resolution);
const width = Math.round(height * (16 / 9)); const width = Math.round(height * (16 / 9));
const conn = [...MediaEngineStore.getMediaEngine().connections].find( const conn = [...MediaEngineStore.getMediaEngine().connections].find(

View file

@ -6,6 +6,7 @@
import { Logger } from "@vencord/types/utils"; import { Logger } from "@vencord/types/utils";
import { currentSettings } from "renderer/components/ScreenSharePicker"; import { currentSettings } from "renderer/components/ScreenSharePicker";
import { State } from "renderer/settings";
import { isLinux } from "renderer/utils"; import { isLinux } from "renderer/utils";
const logger = new Logger("VesktopStreamFixes"); const logger = new Logger("VesktopStreamFixes");
@ -27,8 +28,8 @@ if (isLinux) {
const stream = await original.call(this, opts); const stream = await original.call(this, opts);
const id = await getVirtmic(); const id = await getVirtmic();
const frameRate = Number(currentSettings?.fps); const frameRate = Number(State.store.screenshareQuality?.frameRate ?? 30);
const height = Number(currentSettings?.resolution); const height = Number(State.store.screenshareQuality?.resolution ?? 720);
const width = Math.round(height * (16 / 9)); const width = Math.round(height * (16 / 9));
const track = stream.getVideoTracks()[0]; const track = stream.getVideoTracks()[0];

View file

@ -7,6 +7,9 @@
import { useEffect, useReducer } from "@vencord/types/webpack/common"; import { useEffect, useReducer } from "@vencord/types/webpack/common";
import { SettingsStore } from "shared/utils/SettingsStore"; import { SettingsStore } from "shared/utils/SettingsStore";
import { VesktopLogger } from "./logger";
import { localStorage } from "./utils";
export const Settings = new SettingsStore(VesktopNative.settings.get()); export const Settings = new SettingsStore(VesktopNative.settings.get());
Settings.addGlobalChangeListener((o, p) => VesktopNative.settings.set(o, p)); Settings.addGlobalChangeListener((o, p) => VesktopNative.settings.set(o, p));
@ -28,3 +31,38 @@ export function getValueAndOnChange(key: keyof typeof Settings.store) {
onChange: (value: any) => (Settings.store[key] = value) onChange: (value: any) => (Settings.store[key] = value)
}; };
} }
interface TState {
screenshareQuality?: {
resolution: string;
frameRate: string;
};
}
const stateKey = "VesktopState";
const currentState: TState = (() => {
const stored = localStorage.getItem(stateKey);
if (!stored) return {};
try {
return JSON.parse(stored);
} catch (e) {
VesktopLogger.error("Failed to parse stored state", e);
return {};
}
})();
export const State = new SettingsStore<TState>(currentState);
State.addGlobalChangeListener((o, p) => localStorage.setItem(stateKey, JSON.stringify(o)));
export function useVesktopState() {
const [, update] = useReducer(x => x + 1, 0);
useEffect(() => {
State.addGlobalChangeListener(update);
return () => State.removeGlobalChangeListener(update);
}, []);
return State.store;
}

View file

@ -4,6 +4,7 @@
* Copyright (c) 2023 Vendicated and Vencord contributors * Copyright (c) 2023 Vendicated and Vencord contributors
*/ */
// Discord deletes this from the window so we need to capture it in a variable
export const { localStorage } = window; export const { localStorage } = window;
export const isFirstRun = (() => { export const isFirstRun = (() => {