A repo trying to gather all info regarding proper screensharing on Discord with Desktop Audio for linux users
691
50 commits
updated May 15, 2025
This repo allows you to Screenshare on Discord with Audio on Linux, on the web app without mixing Screenshare audio and microphone. It does so by redefining your browser's getDisplayMedia function. Technically, this fixes the browser's ability to Screenshare/Screen Capture with Audio in general so it should work on every other video conferencing service that allows for Screensharing from a browser like Jitsi.
Applications which incorporate this tutorial and automate its steps exist to provide a user-friendly experience. These are the following:
pactl info to check whether you use PulseAudio or PipeWire. If you see Server Name: pulseaudio you are on PulseAudio, if you see something along the lines of Server Name: PulseAudio (on PipeWire X.XX.XX) you are on PipeWire. If you use PipeWire your life becomes easier since you can use the tool that automates the process, if you are on regular PulseAudio you'll have to stop at this step and follow the manual steps mentioned here.bash <(curl -s https://gist.githubusercontent.com/edisionnano/4cd912315ae4e309261147be23ed0dee/raw/e03e739e1aee407f2163af096604752f8166ed5c/discord.sh) on your terminal to run my GUI and select a sound source, you can also alias this or save the script locally to run it faster if you want.Tips and notes:
echo $XDG_SESSION_TYPE.chmod +x virtmic and execute it using ./virtmic. The tool now asks you for the name of the app you want to share, to find the name of the app you'll have to use pw-cli ls Node while the app is running. Once you type it simply hit enter and minimize the terminal.Contact me at @samantas5855 (was Samantas5855#2607) on Discord for additional support.
Continue reading if you want to know more about how this works.
Screensharing on Discord has been a huge pain for non Windows users since desktop audio would not be captured and until recently there was no option to even pick one screen if you had multiple. Screensharing with desktop audio was recently fixed on Mac OS on the official client only through a proprietary hack since getting desktop audio on Mac isn't easy and you need something like Soundflower to interact with the kernel and electron/chromium don't have such functionality.
A hack many people do is to mix their microphone with their desktop audio to stream it that way, this is not proper because all users on the call are forced to listen to your desktop audio regardless of whether they are watching your stream or not (a huge issue when many people are streaming), they cannot adjust your stream and microphone volume individually, mic channels overlap on discord when many people are talking, bitrate is low, the sound is mono and effects are applied. Some of these issues may appear in our methods too but it's possible to fix them using JavaScript which is why this repo exists in the first place.
As you probably know Discord's client isn't a native app, it's a web browser since it uses electron that is a modified version of Chromium. You can use Chromium or any Chromium-Based browser like Brave and Vivaldi to log in on Discord and you can even Screenshare but desktop audio will only work on Windows and Linux with Chromium OS Audio Server (so only Chrome/Chromium OS). Linux and Mac users can only share the audio of a tab.
As discussed in the prologue it's hard to get desktop audio on Mac OS because a signed kernel extension is needed so it must be hard on Linux too right? The answer is no, in fact, any app that can capture an input can also capture desktop audio, that's because on PulseAudio (includes PipeWire) there exist some virtual input devices called Monitors. Monitors replicate the audio of an output (like your speakers or headphones) and make it an input. Chromium used to block the capture of Monitor devices but the block has been lifted, while not enabled by default the flag #pulseaudio-loopback-for-screen-share toggles this behaviour. Using a Monitor as Screenshare can be helpful on some services but not Discord since the other users will listen to themselves on the Screenshare stream's audio. If you are familiar with Linux's audio server you can control what Chromium captures through pulseaudio assistant programms like pavucontrol or a patchbay like helvum and qjackctl (we run it using pw-jack qjakckctl on pipewire). A patchbay will let you connect the outputs of processes like games to Chromium's Screenshare input using nodes.
It's a pretty simple one and emanates from the paragraph above. Since Chromium cannot capture a monitor we'll capture a microphone, merge it with the Screenshare stream using JavaScript and then connect the audio of the apps we want using a patchbay like helvum or qjackctl.
As mentioned in the prologue there are still some issues to be resolved.
Below, the Javascript code used to achieve Screensharing with Audio:
// ==UserScript==
// @name Screenshare with Audio
// @namespace https://github.com/edisionnano
// @version 0.6
// @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: 11 */
(async () => {
'use strict';
async function getVirtMicTrack() {
await navigator.mediaDevices.getUserMedia({
audio: true
});
await new Promise(r => setTimeout(r, 500));
const devices = await navigator.mediaDevices.enumerateDevices();
const virtmic = devices.find(d => d.kind === "audioinput" && d.label.toLowerCase().includes("virtmic"));
if (!virtmic) {
return null;
}
const stream = await navigator.mediaDevices.getUserMedia({
audio: {
deviceId: {
exact: virtmic.deviceId
},
channelCount: 2,
autoGainControl: false,
echoCancellation: false,
noiseSuppression: false
// You can set more audio constraints here, bellow are some examples
// just don't forget to add a comma at every option other than the last
//latency: 0,
//sampleRate: 48000,
//sampleSize: 16,
//volume: 1.0
}
});
return stream.getAudioTracks()[0];
}
const hookDisplayMedia = async () => {
if (!navigator.mediaDevices?.getDisplayMedia) {
return;
}
const original = navigator.mediaDevices.getDisplayMedia;
navigator.mediaDevices.getDisplayMedia = async function(constraints) {
const displayStream = await original.call(navigator.mediaDevices, constraints);
const virtmicTrack = await getVirtMicTrack();
if (!virtmicTrack) return displayStream;
displayStream.getAudioTracks().forEach(track => displayStream.removeTrack(track));
displayStream.addTrack(virtmicTrack);
return displayStream;
};
};
let retries = 0;
const interval = setInterval(() => {
if (retries++ > 10) {
clearInterval(interval);
return;
}
if (navigator.mediaDevices?.getDisplayMedia) {
clearInterval(interval);
hookDisplayMedia();
}
}, 500);
//Stereo support thanks to @dakrk
const originalSetRemoteDescription = RTCPeerConnection.prototype.setRemoteDescription;
RTCPeerConnection.prototype.setRemoteDescription = function(...args) {
if (args[0]?.sdp) {
args[0].sdp = args[0].sdp.replaceAll('useinbandfec=1', 'useinbandfec=1;stereo=1');
}
return originalSetRemoteDescription.apply(this, args);
};
})();
For this to work, you need to make sure Discord doesn't capture the microphone called Default in your language, change that on Discord's Voice & Video settings.
The script is hosted on GreasyFork and OpenUserJS and disables your browser's awful audio processing only for the desktop stream. If you want to disable them for your microphone too you can do so from Discord's Voice & Video settings or you can use this userscript that disables them globally.
After you've configured the script you'll probably want to stream some audio but without capturing your browser's output. Since there's no dedicated app for this yet, we can do it easily with the terminal.
Case A: Capturing all desktop audio minus the browser
export LC_ALL=CDEFAULT_OUTPUT=$(pactl info|sed -n -e 's/^.*Default Sink: //p')pactl load-module module-null-sink sink_name=chromiumpactl load-module module-null-sink sink_name=desktop_audiopactl list sink-inputs and copy the sink id corresponding to your browser.pactl move-sink-input $INDEX chromium
where $INDEX is the index id from the previous command.pactl unload-module module-stream-restorepactl set-default-sink desktop_audio
pactl unload-module module-default-device-restorepactl load-module module-loopback source=desktop_audio.monitor sink=chromiumpactl load-module module-loopback source=chromium.monitor sink=$DEFAULT_OUTPUTpactl load-module module-remap-source master=desktop_audio.monitor source_name=virtmic source_properties=device.description=virtmicnohup pw-loopback --capture-props='node.target=desktop_audio' --playback-props='media.class=Audio/Source node.name=virtmic node.description="virtmic"' >/dev/null &pulseaudio -ksystemctl --user restart pipewireAfter you've done this once you can then create a file called desktop.sh including
export LC_ALL=C
DEFAULT_OUTPUT=$(pactl info|sed -n -e 's/^.*Default Sink: //p')
if ! pactl info|grep -w "PipeWire">/dev/null; then
pactl unload-module module-default-device-restore
fi
pactl load-module module-null-sink sink_name=chromium
pactl load-module module-null-sink sink_name=desktop_audio
pactl set-default-sink desktop_audio
pactl load-module module-loopback source=desktop_audio.monitor sink=$DEFAULT_OUTPUT
pactl load-module module-loopback source=chromium.monitor sink=$DEFAULT_OUTPUT
if pactl info|grep -w "PipeWire">/dev/null; then
nohup pw-loopback --capture-props='node.target=desktop_audio' --playback-props='media.class=Audio/Source node.name=virtmic node.description="virtmic"' >/dev/null &
else
pactl load-module module-remap-source master=desktop_audio.monitor source_name=virtmic source_properties=device.description=virtmic
fi
and run it using sh desktop.sh every time you want to screenshare with your whole desktop's audio.
This should work for both PulseAudio and PipeWire.
Case A Tips:
Case B: Sharing audio from one (or more) specific application(s)
Most stuff from above still apply on this case but this one is a bit simpler as we move apps seperately instead of everything minus one app.
export LC_ALL=CDEFAULT_OUTPUT=$(pactl info|sed -n -e 's/^.*Default Sink: //p')minecraft, I should run:SINK_NAME=minecraftminecraft with the sink name of your choice.pactl load-module module-null-sink sink_name=$SINK_NAMEminecraft it will place the app there automatically even if it's not running or playing audio. This is very handy cause you can have multiple configurations that you can load on the fly. If, for some reason, you want to disable this feature on PulseAudio (not for PipeWire users) run:pactl unload-module module-stream-restorepactl list sink-inputspactl move-sink-input command. For example, Minecraft has Sink Input ID #1 and I want to move it to the minecraft sink. The appropriate command would be:pactl move-sink-input 1 $SINK_NAMEpactl move-sink-input 2 $SINK_NAMEpactl load-module module-loopback source=$SINK_NAME.monitor sink=$DEFAULT_OUTPUTpactl load-module module-remap-source master=$SINK_NAME.monitor source_name=virtmic source_properties=device.description=virtmicnohup pw-loopback --capture-props='node.target='$SINK_NAME --playback-props='media.class=Audio/Source node.name=virtmic node.description="virtmic"' >/dev/null &pulseaudio -ksystemctl --user restart pipewireAfter finishing the tutorial successfully at least once you can create a shell script to do everything with just one command. You can name it application.sh and add the following
SINK_NAME=
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 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
Then either give SINK_NAME= a value, have different shell scripts for every sink and run them using sh application.sh (replace application.sh if you named it otherwise) OR delete the first line and call the script with a parameter, for example for me that I made the minecraft sink I'd do
SINK_NAME=minecraft sh application.sh
Case B Tips:
Getting the script to use virtmic
The script searches for virtmic by default and only if that's not found does it fallback to Default. Just make sure you have the latest version of the script and that neither Default nor virtmic are selected as input devices on Discord's "Voice & Video" settings.
From Firefox version 101 onward the script works out of the box as long as a microphone named virtmic exists since Firefox has no device with id default. However when streaming from Firefox there do exist severe audio quality/pitching issues on 101, these issues were fixed on later versions.
66 followers · starred Dec 2021
163 followers · starred Jan 2026
70 followers · starred Jul 2022
75 followers · starred Apr 2022
A repo trying to gather all info regarding proper screensharing on Discord with Desktop Audio for linux users
691
50 commits
updated May 15, 2025
This repo allows you to Screenshare on Discord with Audio on Linux, on the web app without mixing Screenshare audio and microphone. It does so by redefining your browser's getDisplayMedia function. Technically, this fixes the browser's ability to Screenshare/Screen Capture with Audio in general so it should work on every other video conferencing service that allows for Screensharing from a browser like Jitsi.
Applications which incorporate this tutorial and automate its steps exist to provide a user-friendly experience. These are the following:
pactl info to check whether you use PulseAudio or PipeWire. If you see Server Name: pulseaudio you are on PulseAudio, if you see something along the lines of Server Name: PulseAudio (on PipeWire X.XX.XX) you are on PipeWire. If you use PipeWire your life becomes easier since you can use the tool that automates the process, if you are on regular PulseAudio you'll have to stop at this step and follow the manual steps mentioned here.bash <(curl -s https://gist.githubusercontent.com/edisionnano/4cd912315ae4e309261147be23ed0dee/raw/e03e739e1aee407f2163af096604752f8166ed5c/discord.sh) on your terminal to run my GUI and select a sound source, you can also alias this or save the script locally to run it faster if you want.Tips and notes:
echo $XDG_SESSION_TYPE.chmod +x virtmic and execute it using ./virtmic. The tool now asks you for the name of the app you want to share, to find the name of the app you'll have to use pw-cli ls Node while the app is running. Once you type it simply hit enter and minimize the terminal.Contact me at @samantas5855 (was Samantas5855#2607) on Discord for additional support.
Continue reading if you want to know more about how this works.
Screensharing on Discord has been a huge pain for non Windows users since desktop audio would not be captured and until recently there was no option to even pick one screen if you had multiple. Screensharing with desktop audio was recently fixed on Mac OS on the official client only through a proprietary hack since getting desktop audio on Mac isn't easy and you need something like Soundflower to interact with the kernel and electron/chromium don't have such functionality.
A hack many people do is to mix their microphone with their desktop audio to stream it that way, this is not proper because all users on the call are forced to listen to your desktop audio regardless of whether they are watching your stream or not (a huge issue when many people are streaming), they cannot adjust your stream and microphone volume individually, mic channels overlap on discord when many people are talking, bitrate is low, the sound is mono and effects are applied. Some of these issues may appear in our methods too but it's possible to fix them using JavaScript which is why this repo exists in the first place.
As you probably know Discord's client isn't a native app, it's a web browser since it uses electron that is a modified version of Chromium. You can use Chromium or any Chromium-Based browser like Brave and Vivaldi to log in on Discord and you can even Screenshare but desktop audio will only work on Windows and Linux with Chromium OS Audio Server (so only Chrome/Chromium OS). Linux and Mac users can only share the audio of a tab.
As discussed in the prologue it's hard to get desktop audio on Mac OS because a signed kernel extension is needed so it must be hard on Linux too right? The answer is no, in fact, any app that can capture an input can also capture desktop audio, that's because on PulseAudio (includes PipeWire) there exist some virtual input devices called Monitors. Monitors replicate the audio of an output (like your speakers or headphones) and make it an input. Chromium used to block the capture of Monitor devices but the block has been lifted, while not enabled by default the flag #pulseaudio-loopback-for-screen-share toggles this behaviour. Using a Monitor as Screenshare can be helpful on some services but not Discord since the other users will listen to themselves on the Screenshare stream's audio. If you are familiar with Linux's audio server you can control what Chromium captures through pulseaudio assistant programms like pavucontrol or a patchbay like helvum and qjackctl (we run it using pw-jack qjakckctl on pipewire). A patchbay will let you connect the outputs of processes like games to Chromium's Screenshare input using nodes.
It's a pretty simple one and emanates from the paragraph above. Since Chromium cannot capture a monitor we'll capture a microphone, merge it with the Screenshare stream using JavaScript and then connect the audio of the apps we want using a patchbay like helvum or qjackctl.
As mentioned in the prologue there are still some issues to be resolved.
Below, the Javascript code used to achieve Screensharing with Audio:
// ==UserScript==
// @name Screenshare with Audio
// @namespace https://github.com/edisionnano
// @version 0.6
// @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: 11 */
(async () => {
'use strict';
async function getVirtMicTrack() {
await navigator.mediaDevices.getUserMedia({
audio: true
});
await new Promise(r => setTimeout(r, 500));
const devices = await navigator.mediaDevices.enumerateDevices();
const virtmic = devices.find(d => d.kind === "audioinput" && d.label.toLowerCase().includes("virtmic"));
if (!virtmic) {
return null;
}
const stream = await navigator.mediaDevices.getUserMedia({
audio: {
deviceId: {
exact: virtmic.deviceId
},
channelCount: 2,
autoGainControl: false,
echoCancellation: false,
noiseSuppression: false
// You can set more audio constraints here, bellow are some examples
// just don't forget to add a comma at every option other than the last
//latency: 0,
//sampleRate: 48000,
//sampleSize: 16,
//volume: 1.0
}
});
return stream.getAudioTracks()[0];
}
const hookDisplayMedia = async () => {
if (!navigator.mediaDevices?.getDisplayMedia) {
return;
}
const original = navigator.mediaDevices.getDisplayMedia;
navigator.mediaDevices.getDisplayMedia = async function(constraints) {
const displayStream = await original.call(navigator.mediaDevices, constraints);
const virtmicTrack = await getVirtMicTrack();
if (!virtmicTrack) return displayStream;
displayStream.getAudioTracks().forEach(track => displayStream.removeTrack(track));
displayStream.addTrack(virtmicTrack);
return displayStream;
};
};
let retries = 0;
const interval = setInterval(() => {
if (retries++ > 10) {
clearInterval(interval);
return;
}
if (navigator.mediaDevices?.getDisplayMedia) {
clearInterval(interval);
hookDisplayMedia();
}
}, 500);
//Stereo support thanks to @dakrk
const originalSetRemoteDescription = RTCPeerConnection.prototype.setRemoteDescription;
RTCPeerConnection.prototype.setRemoteDescription = function(...args) {
if (args[0]?.sdp) {
args[0].sdp = args[0].sdp.replaceAll('useinbandfec=1', 'useinbandfec=1;stereo=1');
}
return originalSetRemoteDescription.apply(this, args);
};
})();
For this to work, you need to make sure Discord doesn't capture the microphone called Default in your language, change that on Discord's Voice & Video settings.
The script is hosted on GreasyFork and OpenUserJS and disables your browser's awful audio processing only for the desktop stream. If you want to disable them for your microphone too you can do so from Discord's Voice & Video settings or you can use this userscript that disables them globally.
After you've configured the script you'll probably want to stream some audio but without capturing your browser's output. Since there's no dedicated app for this yet, we can do it easily with the terminal.
Case A: Capturing all desktop audio minus the browser
export LC_ALL=CDEFAULT_OUTPUT=$(pactl info|sed -n -e 's/^.*Default Sink: //p')pactl load-module module-null-sink sink_name=chromiumpactl load-module module-null-sink sink_name=desktop_audiopactl list sink-inputs and copy the sink id corresponding to your browser.pactl move-sink-input $INDEX chromium
where $INDEX is the index id from the previous command.pactl unload-module module-stream-restorepactl set-default-sink desktop_audio
pactl unload-module module-default-device-restorepactl load-module module-loopback source=desktop_audio.monitor sink=chromiumpactl load-module module-loopback source=chromium.monitor sink=$DEFAULT_OUTPUTpactl load-module module-remap-source master=desktop_audio.monitor source_name=virtmic source_properties=device.description=virtmicnohup pw-loopback --capture-props='node.target=desktop_audio' --playback-props='media.class=Audio/Source node.name=virtmic node.description="virtmic"' >/dev/null &pulseaudio -ksystemctl --user restart pipewireAfter you've done this once you can then create a file called desktop.sh including
export LC_ALL=C
DEFAULT_OUTPUT=$(pactl info|sed -n -e 's/^.*Default Sink: //p')
if ! pactl info|grep -w "PipeWire">/dev/null; then
pactl unload-module module-default-device-restore
fi
pactl load-module module-null-sink sink_name=chromium
pactl load-module module-null-sink sink_name=desktop_audio
pactl set-default-sink desktop_audio
pactl load-module module-loopback source=desktop_audio.monitor sink=$DEFAULT_OUTPUT
pactl load-module module-loopback source=chromium.monitor sink=$DEFAULT_OUTPUT
if pactl info|grep -w "PipeWire">/dev/null; then
nohup pw-loopback --capture-props='node.target=desktop_audio' --playback-props='media.class=Audio/Source node.name=virtmic node.description="virtmic"' >/dev/null &
else
pactl load-module module-remap-source master=desktop_audio.monitor source_name=virtmic source_properties=device.description=virtmic
fi
and run it using sh desktop.sh every time you want to screenshare with your whole desktop's audio.
This should work for both PulseAudio and PipeWire.
Case A Tips:
Case B: Sharing audio from one (or more) specific application(s)
Most stuff from above still apply on this case but this one is a bit simpler as we move apps seperately instead of everything minus one app.
export LC_ALL=CDEFAULT_OUTPUT=$(pactl info|sed -n -e 's/^.*Default Sink: //p')minecraft, I should run:SINK_NAME=minecraftminecraft with the sink name of your choice.pactl load-module module-null-sink sink_name=$SINK_NAMEminecraft it will place the app there automatically even if it's not running or playing audio. This is very handy cause you can have multiple configurations that you can load on the fly. If, for some reason, you want to disable this feature on PulseAudio (not for PipeWire users) run:pactl unload-module module-stream-restorepactl list sink-inputspactl move-sink-input command. For example, Minecraft has Sink Input ID #1 and I want to move it to the minecraft sink. The appropriate command would be:pactl move-sink-input 1 $SINK_NAMEpactl move-sink-input 2 $SINK_NAMEpactl load-module module-loopback source=$SINK_NAME.monitor sink=$DEFAULT_OUTPUTpactl load-module module-remap-source master=$SINK_NAME.monitor source_name=virtmic source_properties=device.description=virtmicnohup pw-loopback --capture-props='node.target='$SINK_NAME --playback-props='media.class=Audio/Source node.name=virtmic node.description="virtmic"' >/dev/null &pulseaudio -ksystemctl --user restart pipewireAfter finishing the tutorial successfully at least once you can create a shell script to do everything with just one command. You can name it application.sh and add the following
SINK_NAME=
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 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
Then either give SINK_NAME= a value, have different shell scripts for every sink and run them using sh application.sh (replace application.sh if you named it otherwise) OR delete the first line and call the script with a parameter, for example for me that I made the minecraft sink I'd do
SINK_NAME=minecraft sh application.sh
Case B Tips:
Getting the script to use virtmic
The script searches for virtmic by default and only if that's not found does it fallback to Default. Just make sure you have the latest version of the script and that neither Default nor virtmic are selected as input devices on Discord's "Voice & Video" settings.
From Firefox version 101 onward the script works out of the box as long as a microphone named virtmic exists since Firefox has no device with id default. However when streaming from Firefox there do exist severe audio quality/pitching issues on 101, these issues were fixed on later versions.
66 followers · starred Dec 2021
163 followers · starred Jan 2026
70 followers · starred Jul 2022
75 followers · starred Apr 2022