[wip] linux audio screenshare

This commit is contained in:
smartfrigde 2023-06-19 17:49:09 +02:00
parent bed9e02059
commit 1a1c549f6c
4 changed files with 290 additions and 22 deletions

View file

@ -0,0 +1,65 @@
navigator.mediaDevices.getDisplayMedia = getDisplayMedia;
// ==UserScript==
// @name Screenshare with Audio
// @namespace https://github.com/edisionnano
// @version 0.4
// @updateURL https://openuserjs.org/meta/samantas5855/Screenshare_with_Audio.meta.js
// @description Screenshare with Audio on Discord
// @author Guest271314 and Samantas5855
// @match https://*.discord.com/*
// @icon https://www.google.com/s2/favicons?domain=discord.com
// @grant none
// @license MIT
// ==/UserScript==
/* jshint esversion: 8 */
navigator.mediaDevices.chromiumGetDisplayMedia = navigator.mediaDevices.getDisplayMedia;
const getAudioDevice = async (nameOfAudioDevice) => {
await navigator.mediaDevices.getUserMedia({
audio: true
});
await new Promise((r) => setTimeout(r, 1000));
let devices = await navigator.mediaDevices.enumerateDevices();
let audioDevice = devices.find(({label}) => label === nameOfAudioDevice);
return audioDevice;
};
const getDisplayMedia = async () => {
var id;
try {
let myDiscordAudioSink = await getAudioDevice("screenshareAudio");
id = myDiscordAudioSink.deviceId;
} catch (error) {
id = "default";
}
let captureSystemAudioStream = await navigator.mediaDevices.getUserMedia({
audio: {
// We add our audio constraints here, to get a list of supported constraints use navigator.mediaDevices.getSupportedConstraints();
// We must capture a microphone, we use default since its the only deviceId that is the same for every Chromium user
deviceId: {
exact: id
},
// We want auto gain control, noise cancellation and noise suppression disabled so that our stream won't sound bad
autoGainControl: false,
echoCancellation: false,
noiseSuppression: false
// By default Chromium sets channel count for audio devices to 1, we want it to be stereo in case we find a way for Discord to accept stereo screenshare too
//channelCount: 2,
// You can set more audio constraints here, bellow are some examples
//latency: 0,
//sampleRate: 48000,
//sampleSize: 16,
//volume: 1.0
}
});
let [track] = captureSystemAudioStream.getAudioTracks();
const gdm = await navigator.mediaDevices.chromiumGetDisplayMedia({
video: true,
audio: true
});
gdm.addTrack(track);
return gdm;
};
navigator.mediaDevices.getDisplayMedia = getDisplayMedia;

79
src/screenshare/audio.ts Normal file
View file

@ -0,0 +1,79 @@
import {spawn} from "child_process";
import fs from "fs";
import {app} from "electron";
import path from "path";
// quick and dirty way for audio screenshare on Linux
// please PR a better non-shell solution that isn't a dependency mess
//src https://github.com/edisionnano/Screenshare-with-audio-on-Discord-with-Linux
interface SinkInput {
id: string;
properties: Record<string, string>;
}
const parseSinkInputs = (input: string): SinkInput[] => {
const regex = /Sink Input #(\d+)\n([\s\S]+?)(?=\nSink Input #|\n{2,}|$)/g;
const result: SinkInput[] = [];
let match;
while ((match = regex.exec(input))) {
const sinkInput: SinkInput = {
id: match[1],
properties: {}
};
const propertiesRegex = /(\w+)\s*=\s*"([^"]*)"/g;
let propertiesMatch;
while ((propertiesMatch = propertiesRegex.exec(match[2]))) {
sinkInput.properties[propertiesMatch[1]] = propertiesMatch[2];
}
result.push(sinkInput);
}
return result;
};
export function createVirtualDevice(sinkInput: number) {
var script = `
SINK_NAME=armcord
export LC_ALL=C
DEFAULT_OUTPUT=$(pactl info|sed -n -e 's/^.*Default Sink: //p')
pactl load-module module-null-sink sink_name=$SINK_NAME
pactl move-sink-input ${sinkInput} $SINK_NAME
pactl load-module module-loopback source=$SINK_NAME.monitor sink=$DEFAULT_OUTPUT
if pactl info|grep -w "PipeWire">/dev/null; then
nohup pw-loopback --capture-props='node.target='$SINK_NAME --playback-props='media.class=Audio/Source node.name=virtmic node.description="virtmic"' >/dev/null &
else
pactl load-module module-remap-source master=$SINK_NAME.monitor source_name=virtmic source_properties=device.description=virtmic
fi`;
let scriptPath = path.join(app.getPath("temp"), "/", "script.sh");
spawn("chmod +x " + scriptPath);
fs.writeFileSync(scriptPath, script, "utf-8");
const exec = spawn(scriptPath);
}
export function isAudioSupported(): boolean {
const pactl = spawn("pactl");
pactl.on("close", (code) => {
if (code == 0) {
return true;
}
});
return false;
}
export function getSinks() {
const pactl = spawn("pactl list sink-inputs");
pactl.stderr.on("data", (data) => {
console.log(data);
return parseSinkInputs(data);
});
pactl.on("close", (code) => {
if (code !== 0) {
throw Error("Couldn't get list of available apps for audio stream");
}
});
}

View file

@ -1,10 +1,18 @@
import {BrowserWindow, desktopCapturer, ipcMain, session} from "electron";
import path from "path";
import {iconPath} from "../main";
import {getSinks, isAudioSupported} from "./audio";
let capturerWindow: BrowserWindow;
function registerCustomHandler(): void {
session.defaultSession.setDisplayMediaRequestHandler(async (request, callback) => {
console.log(request);
if (process.platform == "linux") {
let isAudio = isAudioSupported();
if (isAudio) {
console.log("audio supported");
getSinks();
}
}
const sources = await desktopCapturer.getSources({
types: ["screen", "window"]
});