/* * SPDX-License-Identifier: GPL-3.0 * Vesktop, a desktop app aiming to give you a snappier Discord Experience * Copyright (c) 2023 Vendicated and Vencord contributors */ import "./screenSharePicker.css"; import { closeModal, Modals, openModal, useAwaiter } from "@vencord/types/utils"; import { findStoreLazy } from "@vencord/types/webpack"; import { Button, Card, Forms, Select, Switch, Text, 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; } export interface StreamPick extends StreamSettings { id: string; } interface Source { id: string; name: string; url: string; } let currentSettings: StreamSettings | null = null; addPatch({ patches: [ { find: "this.localWant=", replacement: { match: /this.localWant=/, replace: "$self.patchStreamQuality(this);$&" } } ], 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 }); Object.assign(opts.capture, { framerate, width, height, pixelCount: height * width }); } }); 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") await VesktopNative.virtmic.start(v.audioSource); 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 }) => ( ))}
); } export function StreamSettings({ source, settings, setSettings }: { source: Source; settings: StreamSettings; setSettings: Dispatch>; }) { const [thumb] = useAwaiter(() => 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 => ( ))}
{isWindows && ( setSettings(s => ({ ...s, audio: checked }))} hideBorder className="vcd-screen-picker-audio" > Stream With Audio )} {isLinux && ( setSettings(s => ({ ...s, audioSource: source }))} /> )}
); } function AudioSourcePickerLinux({ audioSource, setAudioSource }: { audioSource?: string; setAudioSource(s: string): void; }) { const [sources, _, loading] = useAwaiter(() => VesktopNative.virtmic.list(), { fallbackValue: [] }); const sourcesWithNone = sources ? ["None", ...sources] : null; return (
Audio {loading && Loading Audio sources...} {sourcesWithNone === null && ( Failed to retrieve Audio Sources. If you would like to stream with Audio, make sure you're using Pipewire, not Pulseaudio )} {sourcesWithNone && (