mirror of
https://github.com/Vencord/Vesktop.git
synced 2025-02-22 21:35:08 +00:00
remember stream resolution & fps settings
This commit is contained in:
parent
9b0503f49d
commit
b620e07445
4 changed files with 69 additions and 27 deletions
|
@ -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(
|
||||||
|
|
|
@ -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];
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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 = (() => {
|
||||||
|
|
Loading…
Add table
Reference in a new issue