Skip to content

Commit

Permalink
add video device patching as well
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanccn committed Nov 5, 2023
1 parent 5187e2e commit 273606b
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 74 deletions.
84 changes: 71 additions & 13 deletions src/renderer/components/ScreenSharePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand All @@ -42,6 +42,7 @@ interface StreamSettings {

export interface StreamPick extends StreamSettings {
id: string;
cameraId?: string;
}

interface Source {
Expand All @@ -50,6 +51,11 @@ interface Source {
url: string;
}

interface Camera {
id: string;
name: string;
}

let currentSettings: StreamSettings | null = null;

addPatch({
Expand Down Expand Up @@ -96,6 +102,7 @@ if (isLinux) {

export function openScreenSharePicker(screens: Source[], skipPicker: boolean) {
let didSubmit = false;

return new Promise<StreamPick>((resolve, reject) => {
const key = openModal(
props => (
Expand All @@ -105,6 +112,12 @@ export function openScreenSharePicker(screens: Source[], skipPicker: boolean) {
submit={async v => {
didSubmit = true;

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();
Expand All @@ -113,8 +126,6 @@ export function openScreenSharePicker(screens: Source[], skipPicker: boolean) {
}
}

if (v.audioDevice) patchAudioWithDevice(v.audioDevice);

resolve(v);
}}
close={() => {
Expand All @@ -134,12 +145,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>
Expand All @@ -149,6 +174,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,
Expand All @@ -171,6 +227,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>
Expand Down Expand Up @@ -257,7 +314,7 @@ function AudioSourceAnyDevice({
audioDevice?: string;
setAudioDevice(s: string | undefined): void;
}) {
const [sources, _, loading] = useAwaiter(
const [sources] = useAwaiter(
() =>
navigator.mediaDevices
.enumerateDevices()
Expand Down Expand Up @@ -341,11 +398,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}>
Expand All @@ -356,7 +410,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)!}
Expand All @@ -369,7 +426,7 @@ function ModalComponent({

<Modals.ModalFooter className="vcd-screen-picker-footer">
<Button
disabled={!selected}
disabled={!selected && !camera}
onClick={() => {
currentSettings = settings;

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

submit({
id: selected!,
cameraId: camera,
...settings
});

Expand Down
2 changes: 1 addition & 1 deletion src/renderer/patches/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@
import "./spellCheck";
import "./platformClass";
import "./windowsTitleBar";
import "./screenShareAudio";
import "./screenSharePatch";
60 changes: 0 additions & 60 deletions src/renderer/patches/screenShareAudio.ts

This file was deleted.

68 changes: 68 additions & 0 deletions src/renderer/patches/screenSharePatch.ts
Original file line number Diff line number Diff line change
@@ -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;
};
};

0 comments on commit 273606b

Please sign in to comment.