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 });