vesktop-stereo-mic

True stereo Discord voice on macOS/Linux via Vesktop — a Vencord userplugin.
Log | Files | Refs | README | LICENSE

index.ts (4326B)


      1 /*
      2  * StereoMic — transmit your Discord voice input in TRUE STEREO (Vesktop / web Discord).
      3  *
      4  * Discord downmixes the mic to mono. On the web/Vesktop client (Chromium WebRTC) the
      5  * fix is pure SDP: advertise Opus stereo on BOTH the local offer and the remote answer
      6  * (the encoder only sends 2ch if the *receiver* signals stereo — Discord's SFU answer
      7  * says mono, so munging the answer is the essential half), plus capture 2 unprocessed
      8  * channels. Built/verified on macOS Vesktop. No native patching, no re-signing.
      9  *
     10  * Drop this folder into Vencord's  src/userplugins/  and build from source.
     11  */
     12 import definePlugin from "@utils/types";
     13 
     14 const BITRATE = 320000; // Opus avg bitrate (music-grade). Max 510000.
     15 
     16 function mungeSdp(sdp: string, tag: string): string {
     17     if (!sdp || !/opus/i.test(sdp)) return sdp;
     18     const rtpmap = sdp.match(/a=rtpmap:(\d+)\s+opus\/48000\/2/i);
     19     if (!rtpmap) return sdp;
     20     const pt = rtpmap[1];
     21     const lines = sdp.split(/\r?\n/);
     22     let touched = false;
     23     for (let i = 0; i < lines.length; i++) {
     24         if (lines[i].startsWith(`a=fmtp:${pt} `)) {
     25             let p = lines[i].slice(`a=fmtp:${pt} `.length);
     26             const set = (k: string, v: number) => {
     27                 const re = new RegExp(`${k}=[^;]*`);
     28                 p = re.test(p) ? p.replace(re, `${k}=${v}`) : `${p};${k}=${v}`;
     29             };
     30             set("stereo", 1);
     31             set("sprop-stereo", 1);
     32             set("maxaveragebitrate", BITRATE);
     33             lines[i] = `a=fmtp:${pt} ${p}`;
     34             touched = true;
     35         }
     36     }
     37     if (touched) console.log(`[StereoMic] munged ${tag} (pt ${pt})`);
     38     return lines.join("\r\n");
     39 }
     40 
     41 let origSetLocal: typeof RTCPeerConnection.prototype.setLocalDescription | undefined;
     42 let origSetRemote: typeof RTCPeerConnection.prototype.setRemoteDescription | undefined;
     43 let origGetUserMedia: typeof navigator.mediaDevices.getUserMedia | undefined;
     44 
     45 export default definePlugin({
     46     name: "StereoMic",
     47     description: "Send your mic/virtual input to a voice channel in true stereo at high bitrate (Opus SDP munge of local+remote descriptions + 2-channel unprocessed capture). For Vesktop / web Discord on macOS & Linux.",
     48     authors: [{ name: "ganten", id: 0n }],
     49 
     50     start() {
     51         const PC = RTCPeerConnection.prototype;
     52         origSetLocal = PC.setLocalDescription;
     53         origSetRemote = PC.setRemoteDescription;
     54 
     55         const wrap = (orig: any, tag: string) =>
     56             function (this: RTCPeerConnection, desc?: any, ...rest: any[]) {
     57                 try {
     58                     if (desc && desc.sdp) {
     59                         const sdp = mungeSdp(desc.sdp, tag);
     60                         desc = typeof desc.toJSON === "function"
     61                             ? { type: desc.type, sdp }
     62                             : Object.assign({}, desc, { sdp });
     63                     }
     64                 } catch (e) {
     65                     console.warn("[StereoMic] munge error", e);
     66                 }
     67                 return orig.call(this, desc, ...rest);
     68             };
     69 
     70         PC.setLocalDescription = wrap(origSetLocal, "setLocalDescription");
     71         PC.setRemoteDescription = wrap(origSetRemote, "setRemoteDescription");
     72 
     73         const md = navigator.mediaDevices;
     74         origGetUserMedia = md.getUserMedia.bind(md);
     75         md.getUserMedia = function (constraints?: MediaStreamConstraints) {
     76             if (constraints && constraints.audio) {
     77                 const a = typeof constraints.audio === "object" ? { ...constraints.audio } : {};
     78                 (a as any).channelCount = 2;
     79                 (a as any).echoCancellation = false;
     80                 (a as any).noiseSuppression = false;
     81                 (a as any).autoGainControl = false;
     82                 constraints = { ...constraints, audio: a };
     83             }
     84             return origGetUserMedia!(constraints);
     85         };
     86 
     87         console.log("[StereoMic] active — (re)join voice to apply. Feed a stereo source into your input device.");
     88     },
     89 
     90     stop() {
     91         if (origSetLocal) RTCPeerConnection.prototype.setLocalDescription = origSetLocal;
     92         if (origSetRemote) RTCPeerConnection.prototype.setRemoteDescription = origSetRemote;
     93         if (origGetUserMedia) navigator.mediaDevices.getUserMedia = origGetUserMedia;
     94         console.log("[StereoMic] stopped.");
     95     },
     96 });