feat: allow patching video & audio devices into screen share

This commit is contained in:
Ryan Cao 2024-11-25 14:05:10 +08:00
parent 5b6c1c6d81
commit 5133e70717
No known key found for this signature in database
GPG key ID: 48C96B2057D71CB1
2 changed files with 123 additions and 15 deletions

View file

@ -21,6 +21,7 @@ import {
} from "@vencord/types/webpack/common";
import { Node } from "@vencord/venmic";
import type { Dispatch, SetStateAction } from "react";
import { patchOverrideDevices } from "renderer/patches/screenShareFixes";
import { addPatch } from "renderer/patches/shared";
import { useSettings } from "renderer/settings";
import { isLinux, isWindows } from "renderer/utils";
@ -47,6 +48,8 @@ interface StreamSettings {
resolution: StreamResolution;
fps: StreamFps;
audio: boolean;
overrideAudioDevice?: string;
overrideVideoDevice?: string;
contentHint?: string;
includeSources?: AudioSources;
excludeSources?: AudioSources;
@ -140,6 +143,11 @@ export function openScreenSharePicker(screens: Source[], skipPicker: boolean) {
}
}
patchOverrideDevices({
audio: v.overrideAudioDevice,
video: v.overrideVideoDevice
});
resolve(v);
}}
close={() => {
@ -325,6 +333,16 @@ function StreamSettings({
}
);
const [audioDevices, , audioDevicesPending] = useAwaiter(
() => navigator.mediaDevices.enumerateDevices().then(g => g.filter(d => d.kind === "audioinput")),
{ fallbackValue: [] }
);
const [videoDevices, , videoDevicesPending] = useAwaiter(
() => navigator.mediaDevices.enumerateDevices().then(g => g.filter(d => d.kind === "videoinput")),
{ fallbackValue: [] }
);
const openSettings = () => {
const key = openModal(props => (
<AudioSettingsModal
@ -428,6 +446,47 @@ function StreamSettings({
</p>
</div>
</div>
<div>
<Forms.FormTitle>
{audioDevicesPending ? "Loading audio devices..." : "Audio devices"}
</Forms.FormTitle>
<Select
options={audioDevices.map(({ label, deviceId }) => ({
label,
value: deviceId
}))}
isSelected={d => settings.overrideAudioDevice === d}
select={d => {
setSettings(v => ({ ...v, overrideAudioDevice: d }));
}}
serialize={String}
popoutPosition="top"
closeOnSelect={true}
isDisabled={audioDevicesPending}
/>
</div>
<div>
<Forms.FormTitle>
{videoDevicesPending ? "Loading video devices..." : "Video devices"}
</Forms.FormTitle>
<Select
options={videoDevices.map(({ label, deviceId }) => ({
label,
value: deviceId
}))}
isSelected={d => settings.overrideVideoDevice === d}
select={d => {
setSettings(v => ({ ...v, overrideVideoDevice: d }));
}}
serialize={String}
popoutPosition="top"
closeOnSelect={true}
isDisabled={videoDevicesPending}
/>
</div>
{isWindows && (
<Switch
value={settings.audio}

View file

@ -10,21 +10,70 @@ import { isLinux } from "renderer/utils";
const logger = new Logger("VesktopStreamFixes");
if (isLinux) {
const original = navigator.mediaDevices.getDisplayMedia;
const original = navigator.mediaDevices.getDisplayMedia;
async function getVirtmic() {
try {
const devices = await navigator.mediaDevices.enumerateDevices();
const audioDevice = devices.find(({ label }) => label === "vencord-screen-share");
return audioDevice?.deviceId;
} catch (error) {
return null;
}
interface OverrideDevices {
audio: string | undefined;
video: string | undefined;
}
let overrideDevices: OverrideDevices = { audio: undefined, video: undefined };
export const patchOverrideDevices = (newOverrideDevices: OverrideDevices) => {
overrideDevices = newOverrideDevices;
};
async function getVirtmic() {
try {
const devices = await navigator.mediaDevices.enumerateDevices();
const audioDevice = devices.find(({ label }) => label === "vencord-screen-share");
return audioDevice?.deviceId;
} catch (error) {
return null;
}
}
navigator.mediaDevices.getDisplayMedia = async function (opts) {
const stream = await original.call(this, opts);
if (overrideDevices.audio) {
const audio = await navigator.mediaDevices.getUserMedia({
audio: {
deviceId: { exact: overrideDevices.audio },
autoGainControl: false,
echoCancellation: false,
noiseSuppression: false
}
});
stream.getAudioTracks().forEach(t => {
t.stop();
stream.removeTrack(t);
});
audio.getAudioTracks().forEach(t => {
stream.addTrack(t);
});
}
navigator.mediaDevices.getDisplayMedia = async function (opts) {
const stream = await original.call(this, opts);
if (overrideDevices.video) {
const video = await navigator.mediaDevices.getUserMedia({
video: {
deviceId: { exact: overrideDevices.video }
}
});
stream.getVideoTracks().forEach(t => {
t.stop();
stream.removeTrack(t);
});
video.getVideoTracks().forEach(t => {
stream.addTrack(t);
});
}
if (isLinux) {
const id = await getVirtmic();
const frameRate = Number(currentSettings?.fps);
@ -63,7 +112,7 @@ if (isLinux) {
});
audio.getAudioTracks().forEach(t => stream.addTrack(t));
}
}
return stream;
};
}
return stream;
};