feat(screenshare): allow to specify audi share granularity

This commit is contained in:
Curve 2024-05-24 22:57:26 +02:00
parent 6b948668b9
commit c29e1c38ff
No known key found for this signature in database
GPG key ID: 460F6C466BD35813
3 changed files with 85 additions and 26 deletions

View file

@ -11,6 +11,7 @@ import { IpcEvents } from "shared/IpcEvents";
import { STATIC_DIR } from "shared/paths"; import { STATIC_DIR } from "shared/paths";
type LinkData = Parameters<PatchBayType["link"]>[0]; type LinkData = Parameters<PatchBayType["link"]>[0];
type Node = Record<string, string>;
let PatchBay: typeof PatchBayType | undefined; let PatchBay: typeof PatchBayType | undefined;
let patchBayInstance: PatchBayType | undefined; let patchBayInstance: PatchBayType | undefined;
@ -66,17 +67,17 @@ function getRendererAudioServicePid() {
); );
} }
ipcMain.handle(IpcEvents.VIRT_MIC_LIST, () => { ipcMain.handle(IpcEvents.VIRT_MIC_LIST, (_, props: string[]) => {
const audioPid = getRendererAudioServicePid(); const audioPid = getRendererAudioServicePid();
const list = obtainVenmic() const list = obtainVenmic()
?.list() ?.list(props)
.filter(s => s["application.process.id"] !== audioPid) .filter(s => s["application.process.id"] !== audioPid);
.map(s => s["application.name"]);
const uniqueTargets = [...new Set(list)]; const sameProps = (x: Node, y: Node) => props.every(prop => x[prop] === y[prop]);
const unique = list?.filter((x, i, array) => array.findIndex(y => sameProps(x, y)) === i);
return list ? { ok: true, targets: uniqueTargets, hasPipewirePulse } : { ok: false, isGlibCxxOutdated }; return list ? { ok: true, targets: unique, hasPipewirePulse } : { ok: false, isGlibCxxOutdated };
}); });
ipcMain.handle(IpcEvents.VIRT_MIC_START, (_, targets: string[], workaround?: boolean) => { ipcMain.handle(IpcEvents.VIRT_MIC_START, (_, targets: string[], workaround?: boolean) => {

View file

@ -61,10 +61,11 @@ export const VesktopNative = {
}, },
/** only available on Linux. */ /** only available on Linux. */
virtmic: { virtmic: {
list: () => list: (props?: string[]) =>
invoke< invoke<
{ ok: false; isGlibCxxOutdated: boolean } | { ok: true; targets: string[]; hasPipewirePulse: boolean } | { ok: false; isGlibCxxOutdated: boolean }
>(IpcEvents.VIRT_MIC_LIST), | { ok: true; targets: Record<string, string>[]; hasPipewirePulse: boolean }
>(IpcEvents.VIRT_MIC_LIST, props),
start: (targets: string[], workaround?: boolean) => invoke<void>(IpcEvents.VIRT_MIC_START, targets, workaround), start: (targets: string[], workaround?: boolean) => invoke<void>(IpcEvents.VIRT_MIC_START, targets, workaround),
startSystem: (workaround?: boolean, onlyDefaultSpeakers?: boolean) => startSystem: (workaround?: boolean, onlyDefaultSpeakers?: boolean) =>
invoke<void>(IpcEvents.VIRT_MIC_START_SYSTEM, workaround, onlyDefaultSpeakers), invoke<void>(IpcEvents.VIRT_MIC_START_SYSTEM, workaround, onlyDefaultSpeakers),

View file

@ -35,10 +35,11 @@ interface StreamSettings {
resolution: StreamResolution; resolution: StreamResolution;
fps: StreamFps; fps: StreamFps;
audio: boolean; audio: boolean;
audioSource?: string; audioSources?: string[];
contentHint?: string; contentHint?: string;
workaround?: boolean; workaround?: boolean;
onlyDefaultSpeakers?: boolean; onlyDefaultSpeakers?: boolean;
selectByProcess?: boolean;
} }
export interface StreamPick extends StreamSettings { export interface StreamPick extends StreamSettings {
@ -118,11 +119,11 @@ export function openScreenSharePicker(screens: Source[], skipPicker: boolean) {
modalProps={props} modalProps={props}
submit={async v => { submit={async v => {
didSubmit = true; didSubmit = true;
if (v.audioSource && v.audioSource !== "None") { if (v.audioSources && v.audioSources?.[0] !== "None") {
if (v.audioSource === "Entire System") { if (v.audioSources?.[0] === "Entire System") {
await VesktopNative.virtmic.startSystem(v.workaround); await VesktopNative.virtmic.startSystem(v.workaround);
} else { } else {
await VesktopNative.virtmic.start([v.audioSource], v.workaround); await VesktopNative.virtmic.start(v.audioSources, v.workaround);
} }
} }
resolve(v); resolve(v);
@ -291,12 +292,14 @@ function StreamSettings({
<div> <div>
{isLinux && ( {isLinux && (
<AudioSourcePickerLinux <AudioSourcePickerLinux
audioSource={settings.audioSource} audioSources={settings.audioSources}
workaround={settings.workaround} workaround={settings.workaround}
onlyDefaultSpeakers={settings.onlyDefaultSpeakers} onlyDefaultSpeakers={settings.onlyDefaultSpeakers}
setAudioSource={source => setSettings(s => ({ ...s, audioSource: source }))} selectByProcess={settings.selectByProcess}
setAudioSources={source => setSettings(s => ({ ...s, audioSources: source }))}
setWorkaround={value => setSettings(s => ({ ...s, workaround: value }))} setWorkaround={value => setSettings(s => ({ ...s, workaround: value }))}
setOnlyDefaultSpeakers={value => setSettings(s => ({ ...s, onlyDefaultSpeakers: value }))} setOnlyDefaultSpeakers={value => setSettings(s => ({ ...s, onlyDefaultSpeakers: value }))}
setSelectByProcess={value => setSettings(s => ({ ...s, selectByProcess: value }))}
/> />
)} )}
</div> </div>
@ -305,29 +308,61 @@ function StreamSettings({
} }
function AudioSourcePickerLinux({ function AudioSourcePickerLinux({
audioSource, audioSources,
workaround, workaround,
onlyDefaultSpeakers, onlyDefaultSpeakers,
setAudioSource, selectByProcess,
setAudioSources,
setWorkaround, setWorkaround,
setOnlyDefaultSpeakers setOnlyDefaultSpeakers
}: { }: {
audioSource?: string; audioSources?: string[];
workaround?: boolean; workaround?: boolean;
onlyDefaultSpeakers?: boolean; onlyDefaultSpeakers?: boolean;
setAudioSource(s: string): void; selectByProcess?: boolean;
setAudioSources(s: string[]): void;
setWorkaround(b: boolean): void; setWorkaround(b: boolean): void;
setOnlyDefaultSpeakers(b: boolean): void; setOnlyDefaultSpeakers(b: boolean): void;
setSelectByProcess(b: boolean): void;
}) { }) {
const [sources, _, loading] = useAwaiter(() => VesktopNative.virtmic.list(), { const properties = selectByProcess ? ["application.process.id", "media.name"] : undefined;
const [sources, _, loading] = useAwaiter(() => VesktopNative.virtmic.list(properties), {
fallbackValue: { ok: true, targets: [], hasPipewirePulse: true } fallbackValue: { ok: true, targets: [], hasPipewirePulse: true }
}); });
const allSources = sources.ok ? ["None", "Entire System", ...sources.targets] : null; const specialSources = ["None", "Entire System"];
const hasPipewirePulse = sources.ok ? sources.hasPipewirePulse : true; const allSources = sources.ok ? [...specialSources, ...sources.targets] : null;
const hasPipewirePulse = sources.ok ? sources.hasPipewirePulse : true;
const [ignorePulseWarning, setIgnorePulseWarning] = useState(false); const [ignorePulseWarning, setIgnorePulseWarning] = useState(false);
const getName = (x: any) => {
if (specialSources.includes(x)) {
return x;
}
if (!selectByProcess) {
return x["application.name"];
}
let name = `${x["application.name"] ?? "Unknown"}`;
if (x["media.name"]) {
name += ` - ${x["media.name"]} `;
}
return `${name} - ${x["application.process.id"]}`;
};
const getValue = (x: any) => {
if (specialSources.includes(x)) {
return x;
}
return x["object.id"];
};
return ( return (
<> <>
<Forms.FormTitle>Audio Settings</Forms.FormTitle> <Forms.FormTitle>Audio Settings</Forms.FormTitle>
@ -355,9 +390,22 @@ function AudioSourcePickerLinux({
{hasPipewirePulse || ignorePulseWarning ? ( {hasPipewirePulse || ignorePulseWarning ? (
allSources && ( allSources && (
<Select <Select
options={allSources.map(s => ({ label: s, value: s, default: s === "None" }))} options={allSources.map(s => ({
isSelected={s => s === audioSource} label: getName(s),
select={setAudioSource} value: getValue(s),
default: s === "None"
}))}
isSelected={s => audioSources?.includes(s) || false}
select={source => {
const updated = [...(audioSources || []), source];
if (updated?.some(x => specialSources.includes(x))) {
setAudioSources([source]);
return;
}
setAudioSources(updated);
}}
serialize={String} serialize={String}
/> />
) )
@ -390,7 +438,7 @@ function AudioSourcePickerLinux({
<Switch <Switch
hideBorder hideBorder
onChange={setOnlyDefaultSpeakers} onChange={setOnlyDefaultSpeakers}
disabled={audioSource !== "Entire System"} disabled={audioSources?.[0] !== "Entire System"}
value={onlyDefaultSpeakers ?? true} value={onlyDefaultSpeakers ?? true}
note={ note={
<> <>
@ -401,6 +449,15 @@ function AudioSourcePickerLinux({
> >
Only Default Speakers Only Default Speakers
</Switch> </Switch>
<Switch
hideBorder
onChange={setOnlyDefaultSpeakers}
disabled={audioSources?.[0] !== "Entire System"}
value={onlyDefaultSpeakers ?? true}
note={<>Allow to select applications more granularly.</>}
>
Granular Selection
</Switch>
</Card> </Card>
</> </>
); );