add video device patching as well
This commit is contained in:
parent
d7441aaee8
commit
0e81ec3984
4 changed files with 150 additions and 80 deletions
|
@ -20,7 +20,7 @@ import {
|
|||
useState
|
||||
} from "@vencord/types/webpack/common";
|
||||
import type { Dispatch, SetStateAction } from "react";
|
||||
import { patchAudioWithDevice } from "renderer/patches/screenShareAudio";
|
||||
import { patchDisplayMedia } from "renderer/patches/screenSharePatch";
|
||||
import { addPatch } from "renderer/patches/shared";
|
||||
import { isLinux, isWindows } from "renderer/utils";
|
||||
|
||||
|
@ -43,6 +43,7 @@ interface StreamSettings {
|
|||
|
||||
export interface StreamPick extends StreamSettings {
|
||||
id: string;
|
||||
cameraId?: string;
|
||||
}
|
||||
|
||||
interface Source {
|
||||
|
@ -51,6 +52,11 @@ interface Source {
|
|||
url: string;
|
||||
}
|
||||
|
||||
interface Camera {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
let currentSettings: StreamSettings | null = null;
|
||||
|
||||
addPatch({
|
||||
|
@ -100,6 +106,7 @@ if (isLinux) {
|
|||
|
||||
export function openScreenSharePicker(screens: Source[], skipPicker: boolean) {
|
||||
let didSubmit = false;
|
||||
|
||||
return new Promise<StreamPick>((resolve, reject) => {
|
||||
const key = openModal(
|
||||
props => (
|
||||
|
@ -109,16 +116,24 @@ export function openScreenSharePicker(screens: Source[], skipPicker: boolean) {
|
|||
submit={async v => {
|
||||
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);
|
||||
patchDisplayMedia({
|
||||
audioId: v.audioDevice,
|
||||
venmic: !!v.audioSource && v.audioSource !== "None",
|
||||
videoId: v.cameraId
|
||||
});
|
||||
|
||||
if (!v.audioDevice && 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);
|
||||
}
|
||||
}
|
||||
|
||||
patchAudioWithDevice(v.audioDevice);
|
||||
|
||||
resolve(v);
|
||||
}
|
||||
|
||||
patchAudioWithDevice(v.audioDevice);
|
||||
|
||||
resolve(v);
|
||||
}}
|
||||
close={() => {
|
||||
props.onClose();
|
||||
|
@ -137,12 +152,26 @@ export function openScreenSharePicker(screens: Source[], skipPicker: boolean) {
|
|||
});
|
||||
}
|
||||
|
||||
function ScreenPicker({ screens, chooseScreen }: { screens: Source[]; chooseScreen: (id: string) => void }) {
|
||||
function ScreenPicker({
|
||||
screens,
|
||||
chooseScreen,
|
||||
isDisabled = false
|
||||
}: {
|
||||
screens: Source[];
|
||||
chooseScreen: (id: string) => void;
|
||||
isDisabled?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<div className="vcd-screen-picker-grid">
|
||||
{screens.map(({ id, name, url }) => (
|
||||
<label key={id}>
|
||||
<input type="radio" name="screen" value={id} onChange={() => chooseScreen(id)} />
|
||||
<input
|
||||
type="radio"
|
||||
name="screen"
|
||||
value={id}
|
||||
onChange={() => chooseScreen(id)}
|
||||
disabled={isDisabled}
|
||||
/>
|
||||
|
||||
<img src={url} alt="" />
|
||||
<Text variant="text-sm/normal">{name}</Text>
|
||||
|
@ -152,6 +181,37 @@ function ScreenPicker({ screens, chooseScreen }: { screens: Source[]; chooseScre
|
|||
);
|
||||
}
|
||||
|
||||
function CameraPicker({
|
||||
camera,
|
||||
chooseCamera
|
||||
}: {
|
||||
camera: string | undefined;
|
||||
chooseCamera: (id: string | undefined) => void;
|
||||
}) {
|
||||
const [cameras] = useAwaiter(
|
||||
() =>
|
||||
navigator.mediaDevices
|
||||
.enumerateDevices()
|
||||
.then(res =>
|
||||
res
|
||||
.filter(d => d.kind === "videoinput")
|
||||
.map(d => ({ id: d.deviceId, name: d.label }) satisfies Camera)
|
||||
),
|
||||
{ fallbackValue: [] }
|
||||
);
|
||||
|
||||
return (
|
||||
<Select
|
||||
clearable={true}
|
||||
options={cameras.map(s => ({ label: s.name, value: s.id }))}
|
||||
isSelected={s => s === camera}
|
||||
select={s => chooseCamera(s)}
|
||||
clear={() => chooseCamera(undefined)}
|
||||
serialize={String}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function StreamSettings({
|
||||
source,
|
||||
settings,
|
||||
|
@ -174,6 +234,7 @@ function StreamSettings({
|
|||
return (
|
||||
<div>
|
||||
<Forms.FormTitle>What you're streaming</Forms.FormTitle>
|
||||
|
||||
<Card className="vcd-screen-picker-card vcd-screen-picker-preview">
|
||||
<img src={thumb} alt="" />
|
||||
<Text variant="text-sm/normal">{source.name}</Text>
|
||||
|
@ -256,7 +317,7 @@ function AudioSourceAnyDevice({
|
|||
audioDevice?: string;
|
||||
setAudioDevice(s: string): void;
|
||||
}) {
|
||||
const [sources, _, loading] = useAwaiter(
|
||||
const [sources] = useAwaiter(
|
||||
() =>
|
||||
navigator.mediaDevices
|
||||
.enumerateDevices()
|
||||
|
@ -358,11 +419,8 @@ function ModalComponent({
|
|||
skipPicker: boolean;
|
||||
}) {
|
||||
const [selected, setSelected] = useState<string | undefined>(skipPicker ? screens[0].id : void 0);
|
||||
const [settings, setSettings] = useState<StreamSettings>({
|
||||
resolution: "1080",
|
||||
fps: "60",
|
||||
audio: true
|
||||
});
|
||||
const [camera, setCamera] = useState<string | undefined>(undefined);
|
||||
const [settings, setSettings] = useState<StreamSettings>({ resolution: "1080", fps: "60", audio: true });
|
||||
|
||||
return (
|
||||
<Modals.ModalRoot {...modalProps}>
|
||||
|
@ -373,7 +431,10 @@ function ModalComponent({
|
|||
|
||||
<Modals.ModalContent className="vcd-screen-picker-modal">
|
||||
{!selected ? (
|
||||
<ScreenPicker screens={screens} chooseScreen={setSelected} />
|
||||
<>
|
||||
<ScreenPicker screens={screens} chooseScreen={setSelected} isDisabled={!!camera} />
|
||||
<CameraPicker camera={camera} chooseCamera={setCamera} />
|
||||
</>
|
||||
) : (
|
||||
<StreamSettings
|
||||
source={screens.find(s => s.id === selected)!}
|
||||
|
@ -386,7 +447,7 @@ function ModalComponent({
|
|||
|
||||
<Modals.ModalFooter className="vcd-screen-picker-footer">
|
||||
<Button
|
||||
disabled={!selected}
|
||||
disabled={!selected && !camera}
|
||||
onClick={() => {
|
||||
currentSettings = settings;
|
||||
|
||||
|
@ -410,6 +471,7 @@ function ModalComponent({
|
|||
|
||||
submit({
|
||||
id: selected!,
|
||||
cameraId: camera,
|
||||
...settings
|
||||
});
|
||||
|
||||
|
|
|
@ -7,6 +7,6 @@
|
|||
// TODO: Possibly auto generate glob if we have more patches in the future
|
||||
import "./enableNotificationsByDefault";
|
||||
import "./platformClass";
|
||||
import "./screenShareAudio";
|
||||
import "./spellCheck";
|
||||
import "./windowsTitleBar";
|
||||
import "./screenSharePatch";
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
/*
|
||||
* 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 { isLinux } from "renderer/utils";
|
||||
|
||||
const original = navigator.mediaDevices.getDisplayMedia;
|
||||
|
||||
export const patchAudioWithDevice = (deviceId?: string) => {
|
||||
if (!deviceId) {
|
||||
navigator.mediaDevices.getDisplayMedia = original;
|
||||
return;
|
||||
}
|
||||
|
||||
navigator.mediaDevices.getDisplayMedia = async function (opts) {
|
||||
const stream = await original.call(this, opts);
|
||||
const audio = await navigator.mediaDevices.getUserMedia({ audio: { deviceId: { exact: deviceId } } });
|
||||
const tracks = audio.getAudioTracks();
|
||||
|
||||
tracks.forEach(t => stream.addTrack(t));
|
||||
console.log(`Patched stream ${stream.id} with ${tracks.length} audio tracks from ${deviceId}`);
|
||||
|
||||
return stream;
|
||||
};
|
||||
};
|
||||
|
||||
if (isLinux) {
|
||||
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);
|
||||
const id = await getVirtmic();
|
||||
|
||||
if (id) {
|
||||
const audio = await navigator.mediaDevices.getUserMedia({
|
||||
audio: {
|
||||
deviceId: {
|
||||
exact: id
|
||||
},
|
||||
autoGainControl: false,
|
||||
echoCancellation: false,
|
||||
noiseSuppression: false
|
||||
}
|
||||
});
|
||||
audio.getAudioTracks().forEach(t => stream.addTrack(t));
|
||||
}
|
||||
|
||||
return stream;
|
||||
};
|
||||
}
|
68
src/renderer/patches/screenSharePatch.ts
Normal file
68
src/renderer/patches/screenSharePatch.ts
Normal file
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* 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 { isLinux } from "renderer/utils";
|
||||
|
||||
const original = navigator.mediaDevices.getDisplayMedia;
|
||||
|
||||
interface ScreenSharePatchOptions {
|
||||
videoId?: string;
|
||||
audioId?: string;
|
||||
venmic?: boolean;
|
||||
}
|
||||
|
||||
async function getVirtmic() {
|
||||
if (!isLinux) throw new Error("getVirtmic can not be called on non-Linux platforms!");
|
||||
|
||||
try {
|
||||
const devices = await navigator.mediaDevices.enumerateDevices();
|
||||
const audioDevice = devices.find(({ label }) => label === "vencord-screen-share");
|
||||
return audioDevice?.deviceId;
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export const patchDisplayMedia = (options: ScreenSharePatchOptions) => {
|
||||
navigator.mediaDevices.getDisplayMedia = async function (apiOptions) {
|
||||
let stream: MediaStream;
|
||||
|
||||
if (options.videoId) {
|
||||
stream = await navigator.mediaDevices.getUserMedia({ video: { deviceId: { exact: options.videoId } } });
|
||||
} else {
|
||||
stream = await original.call(this, apiOptions);
|
||||
}
|
||||
|
||||
if (options.audioId) {
|
||||
const audio = await navigator.mediaDevices.getUserMedia({
|
||||
audio: {
|
||||
deviceId: { exact: options.audioId },
|
||||
autoGainControl: false,
|
||||
echoCancellation: false,
|
||||
noiseSuppression: false
|
||||
}
|
||||
});
|
||||
const tracks = audio.getAudioTracks();
|
||||
tracks.forEach(t => stream.addTrack(t));
|
||||
} else if (options.venmic === true) {
|
||||
const virtmicId = await getVirtmic();
|
||||
|
||||
if (virtmicId) {
|
||||
const audio = await navigator.mediaDevices.getUserMedia({
|
||||
audio: {
|
||||
deviceId: { exact: virtmicId },
|
||||
autoGainControl: false,
|
||||
echoCancellation: false,
|
||||
noiseSuppression: false
|
||||
}
|
||||
});
|
||||
audio.getAudioTracks().forEach(t => stream.addTrack(t));
|
||||
}
|
||||
}
|
||||
|
||||
return stream;
|
||||
};
|
||||
};
|
Reference in a new issue