Skip to content

Commit 273606b

Browse files
committed
add video device patching as well
1 parent 5187e2e commit 273606b

File tree

4 files changed

+140
-74
lines changed

4 files changed

+140
-74
lines changed

src/renderer/components/ScreenSharePicker.tsx

+71-13
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import {
2020
useState
2121
} from "@vencord/types/webpack/common";
2222
import type { Dispatch, SetStateAction } from "react";
23-
import { patchAudioWithDevice } from "renderer/patches/screenShareAudio";
23+
import { patchDisplayMedia } from "renderer/patches/screenSharePatch";
2424
import { addPatch } from "renderer/patches/shared";
2525
import { isLinux, isWindows } from "renderer/utils";
2626

@@ -42,6 +42,7 @@ interface StreamSettings {
4242

4343
export interface StreamPick extends StreamSettings {
4444
id: string;
45+
cameraId?: string;
4546
}
4647

4748
interface Source {
@@ -50,6 +51,11 @@ interface Source {
5051
url: string;
5152
}
5253

54+
interface Camera {
55+
id: string;
56+
name: string;
57+
}
58+
5359
let currentSettings: StreamSettings | null = null;
5460

5561
addPatch({
@@ -96,6 +102,7 @@ if (isLinux) {
96102

97103
export function openScreenSharePicker(screens: Source[], skipPicker: boolean) {
98104
let didSubmit = false;
105+
99106
return new Promise<StreamPick>((resolve, reject) => {
100107
const key = openModal(
101108
props => (
@@ -105,6 +112,12 @@ export function openScreenSharePicker(screens: Source[], skipPicker: boolean) {
105112
submit={async v => {
106113
didSubmit = true;
107114

115+
patchDisplayMedia({
116+
audioId: v.audioDevice,
117+
venmic: !!v.audioSource && v.audioSource !== "None",
118+
videoId: v.cameraId
119+
});
120+
108121
if (!v.audioDevice && v.audioSource && v.audioSource !== "None") {
109122
if (v.audioSource === "Entire System") {
110123
await VesktopNative.virtmic.startSystem();
@@ -113,8 +126,6 @@ export function openScreenSharePicker(screens: Source[], skipPicker: boolean) {
113126
}
114127
}
115128

116-
if (v.audioDevice) patchAudioWithDevice(v.audioDevice);
117-
118129
resolve(v);
119130
}}
120131
close={() => {
@@ -134,12 +145,26 @@ export function openScreenSharePicker(screens: Source[], skipPicker: boolean) {
134145
});
135146
}
136147

137-
function ScreenPicker({ screens, chooseScreen }: { screens: Source[]; chooseScreen: (id: string) => void }) {
148+
function ScreenPicker({
149+
screens,
150+
chooseScreen,
151+
isDisabled = false
152+
}: {
153+
screens: Source[];
154+
chooseScreen: (id: string) => void;
155+
isDisabled?: boolean;
156+
}) {
138157
return (
139158
<div className="vcd-screen-picker-grid">
140159
{screens.map(({ id, name, url }) => (
141160
<label key={id}>
142-
<input type="radio" name="screen" value={id} onChange={() => chooseScreen(id)} />
161+
<input
162+
type="radio"
163+
name="screen"
164+
value={id}
165+
onChange={() => chooseScreen(id)}
166+
disabled={isDisabled}
167+
/>
143168

144169
<img src={url} alt="" />
145170
<Text variant="text-sm/normal">{name}</Text>
@@ -149,6 +174,37 @@ function ScreenPicker({ screens, chooseScreen }: { screens: Source[]; chooseScre
149174
);
150175
}
151176

177+
function CameraPicker({
178+
camera,
179+
chooseCamera
180+
}: {
181+
camera: string | undefined;
182+
chooseCamera: (id: string | undefined) => void;
183+
}) {
184+
const [cameras] = useAwaiter(
185+
() =>
186+
navigator.mediaDevices
187+
.enumerateDevices()
188+
.then(res =>
189+
res
190+
.filter(d => d.kind === "videoinput")
191+
.map(d => ({ id: d.deviceId, name: d.label }) satisfies Camera)
192+
),
193+
{ fallbackValue: [] }
194+
);
195+
196+
return (
197+
<Select
198+
clearable={true}
199+
options={cameras.map(s => ({ label: s.name, value: s.id }))}
200+
isSelected={s => s === camera}
201+
select={s => chooseCamera(s)}
202+
clear={() => chooseCamera(undefined)}
203+
serialize={String}
204+
/>
205+
);
206+
}
207+
152208
function StreamSettings({
153209
source,
154210
settings,
@@ -171,6 +227,7 @@ function StreamSettings({
171227
return (
172228
<div>
173229
<Forms.FormTitle>What you're streaming</Forms.FormTitle>
230+
174231
<Card className="vcd-screen-picker-card vcd-screen-picker-preview">
175232
<img src={thumb} alt="" />
176233
<Text variant="text-sm/normal">{source.name}</Text>
@@ -257,7 +314,7 @@ function AudioSourceAnyDevice({
257314
audioDevice?: string;
258315
setAudioDevice(s: string | undefined): void;
259316
}) {
260-
const [sources, _, loading] = useAwaiter(
317+
const [sources] = useAwaiter(
261318
() =>
262319
navigator.mediaDevices
263320
.enumerateDevices()
@@ -341,11 +398,8 @@ function ModalComponent({
341398
skipPicker: boolean;
342399
}) {
343400
const [selected, setSelected] = useState<string | undefined>(skipPicker ? screens[0].id : void 0);
344-
const [settings, setSettings] = useState<StreamSettings>({
345-
resolution: "1080",
346-
fps: "60",
347-
audio: true
348-
});
401+
const [camera, setCamera] = useState<string | undefined>(undefined);
402+
const [settings, setSettings] = useState<StreamSettings>({ resolution: "1080", fps: "60", audio: true });
349403

350404
return (
351405
<Modals.ModalRoot {...modalProps}>
@@ -356,7 +410,10 @@ function ModalComponent({
356410

357411
<Modals.ModalContent className="vcd-screen-picker-modal">
358412
{!selected ? (
359-
<ScreenPicker screens={screens} chooseScreen={setSelected} />
413+
<>
414+
<ScreenPicker screens={screens} chooseScreen={setSelected} isDisabled={!!camera} />
415+
<CameraPicker camera={camera} chooseCamera={setCamera} />
416+
</>
360417
) : (
361418
<StreamSettings
362419
source={screens.find(s => s.id === selected)!}
@@ -369,7 +426,7 @@ function ModalComponent({
369426

370427
<Modals.ModalFooter className="vcd-screen-picker-footer">
371428
<Button
372-
disabled={!selected}
429+
disabled={!selected && !camera}
373430
onClick={() => {
374431
currentSettings = settings;
375432

@@ -393,6 +450,7 @@ function ModalComponent({
393450

394451
submit({
395452
id: selected!,
453+
cameraId: camera,
396454
...settings
397455
});
398456

src/renderer/patches/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@
88
import "./spellCheck";
99
import "./platformClass";
1010
import "./windowsTitleBar";
11-
import "./screenShareAudio";
11+
import "./screenSharePatch";

src/renderer/patches/screenShareAudio.ts

-60
This file was deleted.
+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* SPDX-License-Identifier: GPL-3.0
3+
* Vesktop, a desktop app aiming to give you a snappier Discord Experience
4+
* Copyright (c) 2023 Vendicated and Vencord contributors
5+
*/
6+
7+
import { isLinux } from "renderer/utils";
8+
9+
const original = navigator.mediaDevices.getDisplayMedia;
10+
11+
interface ScreenSharePatchOptions {
12+
videoId?: string;
13+
audioId?: string;
14+
venmic?: boolean;
15+
}
16+
17+
async function getVirtmic() {
18+
if (!isLinux) throw new Error("getVirtmic can not be called on non-Linux platforms!");
19+
20+
try {
21+
const devices = await navigator.mediaDevices.enumerateDevices();
22+
const audioDevice = devices.find(({ label }) => label === "vencord-screen-share");
23+
return audioDevice?.deviceId;
24+
} catch (error) {
25+
return null;
26+
}
27+
}
28+
29+
export const patchDisplayMedia = (options: ScreenSharePatchOptions) => {
30+
navigator.mediaDevices.getDisplayMedia = async function (apiOptions) {
31+
let stream: MediaStream;
32+
33+
if (options.videoId) {
34+
stream = await navigator.mediaDevices.getUserMedia({ video: { deviceId: { exact: options.videoId } } });
35+
} else {
36+
stream = await original.call(this, apiOptions);
37+
}
38+
39+
if (options.audioId) {
40+
const audio = await navigator.mediaDevices.getUserMedia({
41+
audio: {
42+
deviceId: { exact: options.audioId },
43+
autoGainControl: false,
44+
echoCancellation: false,
45+
noiseSuppression: false
46+
}
47+
});
48+
const tracks = audio.getAudioTracks();
49+
tracks.forEach(t => stream.addTrack(t));
50+
} else if (options.venmic === true) {
51+
const virtmicId = await getVirtmic();
52+
53+
if (virtmicId) {
54+
const audio = await navigator.mediaDevices.getUserMedia({
55+
audio: {
56+
deviceId: { exact: virtmicId },
57+
autoGainControl: false,
58+
echoCancellation: false,
59+
noiseSuppression: false
60+
}
61+
});
62+
audio.getAudioTracks().forEach(t => stream.addTrack(t));
63+
}
64+
}
65+
66+
return stream;
67+
};
68+
};

0 commit comments

Comments
 (0)