diff --git a/src/renderer/components/ScreenSharePicker.tsx b/src/renderer/components/ScreenSharePicker.tsx new file mode 100644 index 0000000..e4b8b12 --- /dev/null +++ b/src/renderer/components/ScreenSharePicker.tsx @@ -0,0 +1,534 @@ +/* + * SPDX-License-Identifier: GPL-3.0 + * Aerocord, a vesktop fork for older microsoft NT releases such as NT 6.0, 6.1, 6.2 and 6.3. + * Credits to vendicated and the rest of the vesktop contribuitors for making Vesktop! + */ + +import "./screenSharePicker.css"; + +import { closeModal, Logger, Margins, Modals, ModalSize, openModal, useAwaiter } from "@vencord/types/utils"; +import { findStoreLazy, onceReady } from "@vencord/types/webpack"; +import { + Button, + Card, + FluxDispatcher, + Forms, + Select, + Switch, + Text, + UserStore, + useState +} from "@vencord/types/webpack/common"; +import type { Dispatch, SetStateAction } from "react"; +import { addPatch } from "renderer/patches/shared"; +import { isLinux, isWindows } from "renderer/utils"; + +const StreamResolutions = ["480", "720", "1080", "1440"] as const; +const StreamFps = ["15", "30", "60"] as const; + +const MediaEngineStore = findStoreLazy("MediaEngineStore"); + +export type StreamResolution = (typeof StreamResolutions)[number]; +export type StreamFps = (typeof StreamFps)[number]; + +interface StreamSettings { + resolution: StreamResolution; + fps: StreamFps; + audio: boolean; + audioSource?: string; + contentHint?: string; + workaround?: boolean; + onlyDefaultSpeakers?: boolean; +} + +export interface StreamPick extends StreamSettings { + id: string; +} + +interface Source { + id: string; + name: string; + url: string; +} + +export let currentSettings: StreamSettings | null = null; + +const logger = new Logger("VesktopScreenShare"); + +addPatch({ + patches: [ + { + find: "this.localWant=", + replacement: { + match: /this.localWant=/, + replace: "$self.patchStreamQuality(this);$&" + } + }, + { + find: "x-google-max-bitrate", + replacement: [ + { + // eslint-disable-next-line no-useless-escape + match: /"x-google-max-bitrate=".concat\(\i\)/, + replace: '"x-google-max-bitrate=".concat("80_000")' + }, + { + match: /;level-asymmetry-allowed=1/, + replace: ";b=AS:800000;level-asymmetry-allowed=1" + } + ] + } + ], + patchStreamQuality(opts: any) { + if (!currentSettings) return; + + const framerate = Number(currentSettings.fps); + const height = Number(currentSettings.resolution); + const width = Math.round(height * (16 / 9)); + + Object.assign(opts, { + bitrateMin: 500000, + bitrateMax: 8000000, + bitrateTarget: 600000 + }); + if (opts?.encode) { + Object.assign(opts.encode, { + framerate, + width, + height, + pixelCount: height * width + }); + } + Object.assign(opts.capture, { + framerate, + width, + height, + pixelCount: height * width + }); + } +}); + +if (isLinux) { + onceReady.then(() => { + FluxDispatcher.subscribe("STREAM_CLOSE", ({ streamKey }: { streamKey: string }) => { + const owner = streamKey.split(":").at(-1); + + if (owner !== UserStore.getCurrentUser().id) { + return; + } + + VesktopNative.virtmic.stop(); + }); + }); +} + +export function openScreenSharePicker(screens: Source[], skipPicker: boolean) { + let didSubmit = false; + return new Promise((resolve, reject) => { + const key = openModal( + props => ( + { + didSubmit = true; + if (v.audioSource && v.audioSource !== "None") { + if (v.audioSource === "Entire System") { + await VesktopNative.virtmic.startSystem(v.workaround); + } else { + await VesktopNative.virtmic.start([v.audioSource], v.workaround); + } + } + resolve(v); + }} + close={() => { + props.onClose(); + if (!didSubmit) reject("Aborted"); + }} + skipPicker={skipPicker} + /> + ), + { + onCloseRequest() { + closeModal(key); + reject("Aborted"); + } + } + ); + }); +} + +function ScreenPicker({ screens, chooseScreen }: { screens: Source[]; chooseScreen: (id: string) => void }) { + return ( +
+ {screens.map(({ id, name, url }) => ( + + ))} +
+ ); +} + +function StreamSettings({ + source, + settings, + setSettings, + skipPicker +}: { + source: Source; + settings: StreamSettings; + setSettings: Dispatch>; + skipPicker: boolean; +}) { + const [thumb] = useAwaiter( + () => (skipPicker ? Promise.resolve(source.url) : VesktopNative.capturer.getLargeThumbnail(source.id)), + { + fallbackValue: source.url, + deps: [source.id] + } + ); + + return ( +
+
+ What you're streaming + + + {source.name} + + + Stream Settings + + +
+
+ Resolution +
+ {StreamResolutions.map(res => ( + + ))} +
+
+ +
+ Frame Rate +
+ {StreamFps.map(fps => ( + + ))} +
+
+
+
+
+ Content Type +
+
+ + +
+
+

+ Choosing "Prefer Clarity" will result in a significantly lower framerate in + exchange for a much sharper and clearer image. +

+
+
+
+
+
+
+ +
+ {isWindows && ( + setSettings(s => ({ ...s, audio: checked }))} + hideBorder + className="vcd-screen-picker-audio" + > + Stream With Audio + + )} + + {isLinux && ( + setSettings(s => ({ ...s, audioSource: source }))} + setWorkaround={value => setSettings(s => ({ ...s, workaround: value }))} + setOnlyDefaultSpeakers={value => setSettings(s => ({ ...s, onlyDefaultSpeakers: value }))} + /> + )} +
+
+ ); +} + +function AudioSourcePickerLinux({ + audioSource, + workaround, + onlyDefaultSpeakers, + setAudioSource, + setWorkaround, + setOnlyDefaultSpeakers +}: { + audioSource?: string; + workaround?: boolean; + onlyDefaultSpeakers?: boolean; + setAudioSource(s: string): void; + setWorkaround(b: boolean): void; + setOnlyDefaultSpeakers(b: boolean): void; +}) { + const [sources, _, loading] = useAwaiter(() => VesktopNative.virtmic.list(), { + fallbackValue: { ok: true, targets: [], hasPipewirePulse: true } + }); + + const allSources = sources.ok ? ["None", "Entire System", ...sources.targets] : null; + const hasPipewirePulse = sources.ok ? sources.hasPipewirePulse : true; + + const [ignorePulseWarning, setIgnorePulseWarning] = useState(false); + + return ( + <> + Audio Settings + + {loading ? ( + Loading Audio Sources... + ) : ( + Audio Source + )} + + {!sources.ok && sources.isGlibCxxOutdated && ( + + Failed to retrieve Audio Sources because your C++ library is too old to run + + venmic + + . See{" "} + + this guide + {" "} + for possible solutions. + + )} + + {hasPipewirePulse || ignorePulseWarning ? ( + allSources && ( +