feat(screenshare): allow to specify audi share granularity
This commit is contained in:
parent
6b948668b9
commit
c29e1c38ff
3 changed files with 85 additions and 26 deletions
|
@ -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) => {
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
Reference in a new issue