justfarissss commited on
Commit
d354b7e
·
verified ·
1 Parent(s): 494021c

Upload 8 files

Browse files
.gitattributes CHANGED
@@ -33,3 +33,6 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ discord_voice.node filter=lfs diff=lfs merge=lfs -text
37
+ gpu_encoder_helper.exe filter=lfs diff=lfs merge=lfs -text
38
+ mediapipe.dll filter=lfs diff=lfs merge=lfs -text
discord_voice.node ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:24f60cc3a6418aa280d0485f96c620388c8c4223da34e50beae7cf4fb4445aef
3
+ size 15471544
gpu_encoder_helper.exe ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e631259dea71b6b54e4ee6bcbc687e47ebacc6ff3afbc3e46612e9a1758c2e64
3
+ size 755128
index.js ADDED
@@ -0,0 +1,581 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* eslint-disable no-console */
2
+ // eslint-disable-next-line import/no-unresolved, import/extensions
3
+ const VoiceEngine = require('./discord_voice.node');
4
+ const fs = require('fs');
5
+ const os = require('os');
6
+ const process = require('process');
7
+ const path = require('path');
8
+
9
+ const isElectronRenderer =
10
+ typeof window !== 'undefined' && window != null && window.DiscordNative && window.DiscordNative.isRenderer;
11
+
12
+ const appSettings = isElectronRenderer ? window.DiscordNative.settings : global.appSettings;
13
+ const features = isElectronRenderer ? window.DiscordNative.features : global.features;
14
+ const mainArgv = isElectronRenderer ? window.DiscordNative.processUtils.getMainArgvSync() : [];
15
+
16
+ let dataDirectory;
17
+ if (isElectronRenderer) {
18
+ try {
19
+ dataDirectory =
20
+ isElectronRenderer && window.DiscordNative.fileManager.getModuleDataPathSync
21
+ ? path.join(window.DiscordNative.fileManager.getModuleDataPathSync(), 'discord_voice')
22
+ : null;
23
+ } catch (e) {
24
+ console.error('Failed to get data directory: ', e);
25
+ }
26
+ if (dataDirectory != null) {
27
+ try {
28
+ fs.mkdirSync(dataDirectory, {recursive: true});
29
+ } catch (e) {
30
+ console.warn("Couldn't create voice data directory ", dataDirectory, ':', e);
31
+ }
32
+ }
33
+ }
34
+
35
+ // Init logging
36
+ const isFileManagerAvailable = window?.DiscordNative?.fileManager;
37
+ const isLogDirAvailable = isFileManagerAvailable?.getAndCreateLogDirectorySync;
38
+ let logDirectory;
39
+ if (isLogDirAvailable) {
40
+ logDirectory = window.DiscordNative.fileManager.getAndCreateLogDirectorySync();
41
+ // TODO If/when we move away from utilizing webRTC logging in voice:
42
+ // This module uses a different approach to the log-level, particularly an integer value rather than a string.
43
+ // We should eventually try to align on the string approach (and querying it from our common settings) used by other modules.
44
+ // logLevel = window.DiscordNative.fileManager.logLevelSync();
45
+ } else {
46
+ console.warn('Unable to find log directory');
47
+ }
48
+
49
+ const defaultAudioSubsystem = process.platform === 'win32' ? 'experimental' : 'standard';
50
+ const audioSubsystem = appSettings
51
+ ? appSettings.getSync('audioSubsystem', defaultAudioSubsystem)
52
+ : defaultAudioSubsystem;
53
+ const offloadAdmControls = appSettings ? appSettings.getSync('offloadAdmControls', false) : false;
54
+ const debugLogging = appSettings ? appSettings.getSync('debugLogging', true) : true;
55
+ const asyncVideoInputDeviceInit = appSettings ? appSettings.getSync('asyncVideoInputDeviceInit', false) : false;
56
+ const asyncClipsSourceDeinit = appSettings ? appSettings.getSync('asyncClipsSourceDeinit', false) : false;
57
+
58
+ function versionGreaterThanOrEqual(v1, v2) {
59
+ const v1parts = v1.split('.').map(Number);
60
+ const v2parts = v2.split('.').map(Number);
61
+
62
+ for (let i = 0; i < Math.max(v1parts.length, v2parts.length); i++) {
63
+ const num1 = i < v1parts.length ? v1parts[i] : 0;
64
+ const num2 = i < v2parts.length ? v2parts[i] : 0;
65
+ if (num1 > num2) return true;
66
+ if (num1 < num2) return false;
67
+ }
68
+ return true;
69
+ }
70
+
71
+ function parseArguments(args) {
72
+ const parsed = {
73
+ 'log-level': -1,
74
+ };
75
+
76
+ const descriptions = {
77
+ 'log-level': 'Logging level.',
78
+ 'use-fake-video-capture': 'Use fake video capture device.',
79
+ 'use-file-for-fake-video-capture': 'Use local file for fake video capture.',
80
+ 'use-fake-audio-capture': 'Use fake audio capture device.',
81
+ 'use-files-for-fake-audio-capture': 'Use local files for fake audio capture.',
82
+ };
83
+
84
+ for (let i = 0; i < args.length; i++) {
85
+ const parts = args[i].split('=');
86
+ const arg = parts[0];
87
+ const inlineValue = parts.slice(1).join('='); // Join the rest back together in case there are '=' in the value
88
+
89
+ function getValue() {
90
+ if (inlineValue !== undefined) {
91
+ return inlineValue;
92
+ }
93
+ return args[++i];
94
+ }
95
+
96
+ switch (arg) {
97
+ case '-h':
98
+ case '--help':
99
+ console.log('Help requested:');
100
+ for (const [key, value] of Object.entries(descriptions)) {
101
+ console.log(`--${key}: ${value}`);
102
+ }
103
+ process.exit(0);
104
+ break;
105
+ case '--log-level':
106
+ parsed['log-level'] = parseInt(getValue(), 10);
107
+ break;
108
+ case '--use-fake-video-capture':
109
+ parsed['use-fake-video-capture'] = true;
110
+ break;
111
+ case '--use-file-for-fake-video-capture':
112
+ parsed['use-file-for-fake-video-capture'] = getValue();
113
+ break;
114
+ case '--use-fake-audio-capture':
115
+ parsed['use-fake-audio-capture'] = true;
116
+ break;
117
+ case '--use-files-for-fake-audio-capture':
118
+ parsed['use-files-for-fake-audio-capture'] = getValue();
119
+ break;
120
+ }
121
+ }
122
+
123
+ return parsed;
124
+ }
125
+
126
+ const argv = parseArguments(mainArgv.slice(1));
127
+ const logLevel = argv['log-level'] === -1 ? (debugLogging ? 2 : -1) : argv['log-level'];
128
+ const useFakeVideoCapture = argv['use-fake-video-capture'];
129
+ const useFileForFakeVideoCapture = argv['use-file-for-fake-video-capture'];
130
+ const useFakeAudioCapture = argv['use-fake-audio-capture'];
131
+ const useFilesForFakeAudioCapture = argv['use-files-for-fake-audio-capture'];
132
+
133
+ features.declareSupported('voice_panning');
134
+ features.declareSupported('voice_multiple_connections');
135
+ features.declareSupported('media_devices');
136
+ features.declareSupported('media_video');
137
+ features.declareSupported('debug_logging');
138
+ features.declareSupported('set_audio_device_by_id');
139
+ features.declareSupported('set_video_device_by_id');
140
+ features.declareSupported('loopback');
141
+ features.declareSupported('experiment_config');
142
+ features.declareSupported('remote_locus_network_control');
143
+ //features.declareSupported('connection_replay');
144
+ features.declareSupported('simulcast');
145
+ features.declareSupported('simulcast_bugfix');
146
+ features.declareSupported('direct_video');
147
+ features.declareSupported('electron_video');
148
+ features.declareSupported('fixed_keyframe_interval');
149
+ features.declareSupported('first_frame_callback');
150
+ features.declareSupported('remote_user_multi_stream');
151
+ features.declareSupported('go_live_hardware');
152
+ features.declareSupported('bandwidth_estimation_experiments');
153
+ features.declareSupported('mls_pairwise_fingerprints');
154
+ features.declareSupported('soundshare');
155
+ features.declareSupported('screen_soundshare');
156
+ features.declareSupported('offload_adm_controls');
157
+ features.declareSupported('audio_codec_red');
158
+ features.declareSupported('sidechain_compression');
159
+ features.declareSupported('async_video_input_device_init');
160
+ features.declareSupported('async_clips_source_deinit');
161
+ features.declareSupported('port_aware_latency_testing');
162
+
163
+ if (process.platform === 'darwin') {
164
+ features.declareSupported('screen_capture_kit');
165
+ if (versionGreaterThanOrEqual(os.release(), '23.0.0')) {
166
+ features.declareSupported('native_screenshare_picker');
167
+ }
168
+ }
169
+
170
+ if (process.platform === 'linux') {
171
+ // from WebRTC DesktopCapturer::IsRunningUnderWayland()
172
+ const sessionType = process.env.XDG_SESSION_TYPE;
173
+ const isUnderWayland = sessionType?.startsWith('wayland') && process.env.WAYLAND_DISPLAY != null;
174
+
175
+ const currentDesktop = process.env.XDG_CURRENT_DESKTOP;
176
+ // we only want to enable the gamescope capturer if we're running in a non-nested gamescope session
177
+ const isUnderGamescope =
178
+ !isUnderWayland && currentDesktop?.includes('gamescope') && process.env.GAMESCOPE_WAYLAND_DISPLAY != null;
179
+ const isVaapiEnabled = VoiceEngine.isVaapiEnabled();
180
+
181
+ if (isUnderWayland) {
182
+ features.declareSupported('native_screenshare_picker');
183
+ }
184
+ if (isVaapiEnabled) {
185
+ features.declareSupported('vaapi');
186
+ }
187
+ if (isUnderGamescope && isVaapiEnabled) {
188
+ // ensure we have access to the pipewire socket
189
+ const runtimeDir = process.env.PIPEWIRE_RUNTIME_DIR || process.env.XDG_RUNTIME_DIR || process.env.USERPROFILE;
190
+ if (runtimeDir) {
191
+ const socketName = runtimeDir + '/' + (process.env.PIPEWIRE_REMOTE || 'pipewire-0');
192
+ const sstat = fs.statSync(socketName, {throwIfNoEntry: false});
193
+ if (sstat && sstat.isSocket()) {
194
+ features.declareSupported('gamescope_capture');
195
+ }
196
+ }
197
+ }
198
+ }
199
+
200
+ if (
201
+ process.platform === 'win32'
202
+ || (process.platform === 'darwin' && versionGreaterThanOrEqual(os.release(), '16.0.0'))
203
+ ) {
204
+ features.declareSupported('mediapipe');
205
+ features.declareSupported('mediapipe_animated');
206
+ }
207
+
208
+ if (process.platform === 'win32' || process.platform === 'darwin' || process.platform === 'linux') {
209
+ features.declareSupported('image_quality_measurement');
210
+ }
211
+
212
+ if (process.platform === 'win32') {
213
+ features.declareSupported('voice_legacy_subsystem');
214
+ features.declareSupported('wumpus_video');
215
+ features.declareSupported('hybrid_video');
216
+ features.declareSupported('elevated_hook');
217
+ features.declareSupported('soundshare_loopback');
218
+ features.declareSupported('screen_previews');
219
+ features.declareSupported('window_previews');
220
+ features.declareSupported('audio_debug_state');
221
+ features.declareSupported('video_effects');
222
+ features.declareSupported('voice_experimental_subsystem');
223
+ features.declareSupported('voice_automatic_subsystem');
224
+ features.declareSupported('voice_subsystem_deferred_switch');
225
+ features.declareSupported('voice_bypass_system_audio_input_processing');
226
+ features.declareSupported('clips');
227
+ }
228
+
229
+ function bindConnectionInstance(instance) {
230
+ return {
231
+ destroy: () => instance.destroy(),
232
+
233
+ setTransportOptions: (options) => instance.setTransportOptions(options),
234
+ setSelfMute: (mute) => instance.setSelfMute(mute),
235
+ setSelfDeafen: (deaf) => instance.setSelfDeafen(deaf),
236
+
237
+ mergeUsers: (users) => instance.mergeUsers(users),
238
+ destroyUser: (userId) => instance.destroyUser(userId),
239
+
240
+ prepareSecureFramesTransition: (transitionId, version, callback) =>
241
+ instance.prepareSecureFramesTransition(transitionId, version, callback),
242
+ prepareSecureFramesEpoch: (epoch, version, groupId) => instance.prepareSecureFramesEpoch(epoch, version, groupId),
243
+ executeSecureFramesTransition: (transitionId) => instance.executeSecureFramesTransition(transitionId),
244
+
245
+ updateMLSExternalSender: (externalSenderPackage) => instance.updateMLSExternalSender(externalSenderPackage),
246
+ getMLSKeyPackage: (callback) => instance.getMLSKeyPackage(callback),
247
+ processMLSProposals: (message, callback) => instance.processMLSProposals(message, callback),
248
+ prepareMLSCommitTransition: (transitionId, commit, callback) =>
249
+ instance.prepareMLSCommitTransition(transitionId, commit, callback),
250
+ processMLSWelcome: (transitionId, welcome, callback) => instance.processMLSWelcome(transitionId, welcome, callback),
251
+ getMLSPairwiseFingerprint: (version, userId, callback) =>
252
+ instance.getMLSPairwiseFingerprint(version, userId, callback),
253
+ setOnMLSFailureCallback: (callback) => instance.setOnMLSFailureCallback(callback),
254
+ setSecureFramesStateUpdateCallback: (callback) => instance.setSecureFramesStateUpdateCallback(callback),
255
+
256
+ setLocalVolume: (userId, volume) => instance.setLocalVolume(userId, volume),
257
+ setLocalMute: (userId, mute) => instance.setLocalMute(userId, mute),
258
+ fastUdpReconnect: () => instance.fastUdpReconnect(),
259
+ setLocalPan: (userId, left, right) => instance.setLocalPan(userId, left, right),
260
+ setDisableLocalVideo: (userId, disabled) => instance.setDisableLocalVideo(userId, disabled),
261
+
262
+ setMinimumOutputDelay: (delay) => instance.setMinimumOutputDelay(delay),
263
+ getEncryptionModes: (callback) => instance.getEncryptionModes(callback),
264
+ configureConnectionRetries: (baseDelay, maxDelay, maxAttempts) =>
265
+ instance.configureConnectionRetries(baseDelay, maxDelay, maxAttempts),
266
+ setOnSpeakingCallback: (callback) => instance.setOnSpeakingCallback(callback),
267
+ setOnNativeMuteToggleCallback: (callback) => instance.setOnNativeMuteToggleCallback?.(callback),
268
+ setOnNativeMuteChangedCallback: (callback) => instance.setOnNativeMuteChangedCallback?.(callback),
269
+ setOnSpeakingWhileMutedCallback: (callback) => instance.setOnSpeakingWhileMutedCallback(callback),
270
+ setPingInterval: (interval) => instance.setPingInterval(interval),
271
+ setPingCallback: (callback) => instance.setPingCallback(callback),
272
+ setPingTimeoutCallback: (callback) => instance.setPingTimeoutCallback(callback),
273
+ setRemoteUserSpeakingStatus: (userId, speaking) => instance.setRemoteUserSpeakingStatus(userId, speaking),
274
+ setRemoteUserCanHavePriority: (userId, canHavePriority) =>
275
+ instance.setRemoteUserCanHavePriority(userId, canHavePriority),
276
+
277
+ setOnVideoCallback: (callback) => instance.setOnVideoCallback(callback),
278
+ setOnFirstFrameCallback: (callback) => instance.setOnFirstFrameCallback(callback),
279
+ setOnFirstFrameDeliveredStatsCallback: (callback) => instance.setOnFirstFrameDeliveredStatsCallback(callback),
280
+ setOnFirstFrameEncryptedStatsCallback: (callback) => instance.setOnFirstFrameEncryptedStatsCallback(callback),
281
+ setVideoBroadcast: (broadcasting) => instance.setVideoBroadcast(broadcasting),
282
+ setDesktopSource: (id, videoHook, type) => instance.setDesktopSource(id, videoHook, type),
283
+ setDesktopSourceWithOptions: (options) => instance.setDesktopSourceWithOptions(options),
284
+ setGoLiveDevices: (options) => instance.setGoLiveDevices(options),
285
+ clearGoLiveDevices: () => instance.clearGoLiveDevices(),
286
+ clearDesktopSource: () => instance.clearDesktopSource(),
287
+ setDesktopSourceStatusCallback: (callback) => instance.setDesktopSourceStatusCallback(callback),
288
+ setOnDesktopSourceEnded: (callback) => instance.setOnDesktopSourceEnded(callback),
289
+ setOnSoundshare: (callback) => instance.setOnSoundshare(callback),
290
+ setOnSoundshareEnded: (callback) => instance.setOnSoundshareEnded(callback),
291
+ setOnSoundshareFailed: (callback) => instance.setOnSoundshareFailed(callback),
292
+ setPTTActive: (active, priority, muteOverride) => instance.setPTTActive(active, priority, muteOverride),
293
+ getStats: (callback) => instance.getStats(callback),
294
+ getFilteredStats: (filter, callback) => instance.getFilteredStats(filter, callback),
295
+ startReplay: () => instance.startReplay(),
296
+ setClipRecordUser: (userId, dataType, shouldRecord) => instance.setClipRecordUser(userId, dataType, shouldRecord),
297
+ setRtcLogMarker: (marker) => instance.setRtcLogMarker(marker),
298
+ startSamplesLocalPlayback: (samplesId, options, channels, callback) =>
299
+ instance.startSamplesLocalPlayback(samplesId, options, channels, callback),
300
+ stopSamplesLocalPlayback: (sourceId) => instance.stopSamplesLocalPlayback(sourceId),
301
+ stopAllSamplesLocalPlayback: () => instance.stopAllSamplesLocalPlayback(),
302
+ setOnVideoEncoderFallbackCallback: (codecName) => instance.setOnVideoEncoderFallbackCallback(codecName),
303
+ setOnRtcpMessageCallback: (callback) => instance.setOnRtcpMessageCallback?.(callback),
304
+ presentDesktopSourcePicker: (style) => instance.presentDesktopSourcePicker(style),
305
+ };
306
+ }
307
+
308
+ VoiceEngine.createTransport = VoiceEngine._createTransport;
309
+
310
+ if (isElectronRenderer) {
311
+ VoiceEngine.setImageDataAllocator((width, height) => new window.ImageData(width, height));
312
+ }
313
+
314
+ VoiceEngine.createVoiceConnectionWithOptions = function (userId, connectionOptions, onConnectCallback) {
315
+ const instance = new VoiceEngine.VoiceConnection(userId, connectionOptions, onConnectCallback);
316
+ return bindConnectionInstance(instance);
317
+ };
318
+ VoiceEngine.createOwnStreamConnectionWithOptions = VoiceEngine.createVoiceConnectionWithOptions;
319
+
320
+ // TODO(dyc): |audioEngineId| is vestigial and does not actually get used.
321
+ // "default" was (we deleted audio engine IDs with the removal of android's
322
+ // separate gameAudio engine) hardcoded within nativelib. update the API to
323
+ // reflect this.
324
+ VoiceEngine.createReplayConnection = function (audioEngineId, callback, replayLog) {
325
+ if (replayLog == null) {
326
+ return null;
327
+ }
328
+
329
+ return bindConnectionInstance(new VoiceEngine.VoiceReplayConnection(replayLog, audioEngineId, callback));
330
+ };
331
+
332
+ const setAudioSubsystemInternal = function (subsystem, forceRestart) {
333
+ if (appSettings == null) {
334
+ log('warn', 'Unable to access app settings.');
335
+ return;
336
+ }
337
+
338
+ appSettings.set('audioSubsystem', subsystem);
339
+
340
+ if (isElectronRenderer) {
341
+ if (forceRestart) {
342
+ // DANGER: any unconditional call to setAudioSubsytem will bootloop if we don't
343
+ // debounce noop changes.
344
+ if (subsystem === audioSubsystem) {
345
+ return;
346
+ }
347
+ window.DiscordNative.app.relaunch();
348
+ } else {
349
+ console.log(`Deferring audio subsystem switch to ${subsystem} until next restart.`);
350
+ }
351
+ }
352
+ };
353
+
354
+ VoiceEngine.setAudioSubsystem = function (subsystem) {
355
+ setAudioSubsystemInternal(subsystem, true);
356
+ };
357
+
358
+ VoiceEngine.queueAudioSubsystem = function (subsystem) {
359
+ setAudioSubsystemInternal(subsystem, false);
360
+ };
361
+
362
+ VoiceEngine.setOffloadAdmControls = function (doOffload) {
363
+ appSettings.set('offloadAdmControls', doOffload);
364
+ };
365
+
366
+ VoiceEngine.setAsyncVideoInputDeviceInitSetting = function (enable) {
367
+ appSettings.set('asyncVideoInputDeviceInit', enable);
368
+ };
369
+
370
+ VoiceEngine.setAsyncClipsSourceDeinitSetting = function (enable) {
371
+ appSettings.set('asyncClipsSourceDeinit', enable);
372
+ };
373
+
374
+ VoiceEngine.setDebugLogging = function (enable) {
375
+ if (appSettings == null) {
376
+ log('warn', 'Unable to access app settings.');
377
+ return;
378
+ }
379
+
380
+ if (debugLogging === enable) {
381
+ return;
382
+ }
383
+
384
+ appSettings.set('debugLogging', enable);
385
+
386
+ if (isElectronRenderer) {
387
+ window.DiscordNative.app.relaunch();
388
+ }
389
+ };
390
+
391
+ VoiceEngine.getDebugLogging = function () {
392
+ return debugLogging;
393
+ };
394
+
395
+ const videoStreams = {};
396
+ const directVideoStreams = {};
397
+
398
+ const ensureCanvasContext = function (sinkId) {
399
+ let canvas = document.getElementById(sinkId);
400
+ if (canvas == null) {
401
+ for (const popout of window.popouts.values()) {
402
+ const element = popout.document != null && popout.document.getElementById(sinkId);
403
+ if (element != null) {
404
+ canvas = element;
405
+ break;
406
+ }
407
+ }
408
+
409
+ if (canvas == null) {
410
+ return null;
411
+ }
412
+ }
413
+
414
+ const context = canvas.getContext('2d');
415
+ if (context == null) {
416
+ log('info', `Failed to initialize context for sinkId ${sinkId}`);
417
+ return null;
418
+ }
419
+
420
+ return context;
421
+ };
422
+
423
+ let activeSinksChangeCallback;
424
+ VoiceEngine.setActiveSinksChangeCallback = function (callback) {
425
+ activeSinksChangeCallback = callback;
426
+ };
427
+
428
+ function notifyActiveSinksChange(streamId) {
429
+ if (activeSinksChangeCallback == null) {
430
+ return;
431
+ }
432
+ const sinks = videoStreams[streamId];
433
+ const hasVideoStreamSink = sinks != null && sinks.size > 0;
434
+ const hasDirectVideoStreamSink = directVideoStreams[streamId] != null;
435
+
436
+ activeSinksChangeCallback(streamId, hasVideoStreamSink || hasDirectVideoStreamSink);
437
+ }
438
+
439
+ // [adill] NB: with context isolation it has become extremely costly (both memory & performance) to provide the image
440
+ // data directly to clients at any reasonably fast interval so we've replaced setVideoOutputSink with a direct canvas
441
+ // renderer via addVideoOutputSink
442
+ const setVideoOutputSink = VoiceEngine.setVideoOutputSink;
443
+ const clearVideoOutputSink = (streamId) => {
444
+ // [adill] NB: if you don't pass a frame callback setVideoOutputSink clears the sink
445
+ setVideoOutputSink(streamId);
446
+ };
447
+ const signalVideoOutputSinkReady = VoiceEngine.signalVideoOutputSinkReady;
448
+ delete VoiceEngine.setVideoOutputSink;
449
+ delete VoiceEngine.signalVideoOutputSinkReady;
450
+
451
+ function addVideoOutputSinkInternal(sinkId, streamId, frameCallback) {
452
+ let sinks = videoStreams[streamId];
453
+ if (sinks == null) {
454
+ sinks = videoStreams[streamId] = new Map();
455
+ }
456
+
457
+ // notifyActiveSinksChange relies on videoStreams having the correct state
458
+ const needsToSubscribeToFrames = sinks.size === 0;
459
+ sinks.set(sinkId, frameCallback);
460
+
461
+ if (needsToSubscribeToFrames) {
462
+ log('info', `Subscribing to frames for streamId ${streamId}`);
463
+ const onFrame = (imageData) => {
464
+ const sinks = videoStreams[streamId];
465
+ if (sinks != null) {
466
+ for (const callback of sinks.values()) {
467
+ if (callback != null) {
468
+ callback(imageData);
469
+ }
470
+ }
471
+ }
472
+ signalVideoOutputSinkReady(streamId);
473
+ };
474
+ setVideoOutputSink(streamId, onFrame, true);
475
+ notifyActiveSinksChange(streamId);
476
+ }
477
+ }
478
+
479
+ VoiceEngine.addVideoOutputSink = function (sinkId, streamId, frameCallback) {
480
+ let canvasContext = null;
481
+ addVideoOutputSinkInternal(sinkId, streamId, (imageData) => {
482
+ if (canvasContext == null) {
483
+ canvasContext = ensureCanvasContext(sinkId);
484
+ if (canvasContext == null) {
485
+ return;
486
+ }
487
+ }
488
+ if (frameCallback != null) {
489
+ frameCallback(imageData.width, imageData.height);
490
+ }
491
+ // [adill] NB: Electron 9+ on macOS would show massive leaks in the the GPU helper process when a non-Discord
492
+ // window completely occludes the Discord window. Adding this tiny readback ameliorates the issue. We tried WebGL
493
+ // rendering which did not exhibit the issue, however, the context limit of 16 was too small to be a real
494
+ // alternative.
495
+ canvasContext.getImageData(0, 0, 1, 1);
496
+ canvasContext.putImageData(imageData, 0, 0);
497
+ });
498
+ };
499
+
500
+ VoiceEngine.removeVideoOutputSink = function (sinkId, streamId) {
501
+ const sinks = videoStreams[streamId];
502
+ if (sinks != null) {
503
+ sinks.delete(sinkId);
504
+ if (sinks.size === 0) {
505
+ delete videoStreams[streamId];
506
+ log('info', `Unsubscribing from frames for streamId ${streamId}`);
507
+ clearVideoOutputSink(streamId);
508
+ notifyActiveSinksChange(streamId);
509
+ }
510
+ }
511
+ };
512
+
513
+ // We wrap the direct video calls so we can keep track of all active
514
+ // video output sinks
515
+ const addDirectVideoOutputSink_ = VoiceEngine.addDirectVideoOutputSink;
516
+ const removeDirectVideoOutputSink_ = VoiceEngine.removeDirectVideoOutputSink;
517
+ VoiceEngine.addDirectVideoOutputSink = function (streamId) {
518
+ log('info', `Subscribing to direct frames for streamId ${streamId}`);
519
+ addDirectVideoOutputSink_(streamId);
520
+ directVideoStreams[streamId] = true;
521
+ notifyActiveSinksChange(streamId);
522
+ };
523
+ VoiceEngine.removeDirectVideoOutputSink = function (streamId) {
524
+ log('info', `Unsubscribing from direct frames for streamId ${streamId}`);
525
+ removeDirectVideoOutputSink_(streamId);
526
+ delete directVideoStreams[streamId];
527
+ notifyActiveSinksChange(streamId);
528
+ };
529
+
530
+ let sinkId = 0;
531
+ VoiceEngine.getNextVideoOutputFrame = function (streamId) {
532
+ const nextVideoFrameSinkId = `getNextVideoFrame_${++sinkId}`;
533
+
534
+ return new Promise((resolve, reject) => {
535
+ setTimeout(() => {
536
+ VoiceEngine.removeVideoOutputSink(nextVideoFrameSinkId, streamId);
537
+ reject(new Error('getNextVideoOutputFrame timeout'));
538
+ }, 5000);
539
+
540
+ addVideoOutputSinkInternal(nextVideoFrameSinkId, streamId, (imageData) => {
541
+ VoiceEngine.removeVideoOutputSink(nextVideoFrameSinkId, streamId);
542
+ resolve({
543
+ width: imageData.width,
544
+ height: imageData.height,
545
+ data: new Uint8ClampedArray(imageData.data.buffer),
546
+ });
547
+ });
548
+ });
549
+ };
550
+
551
+ function log(level, message) {
552
+ const consoleLogFn = (() => {
553
+ if (!['trace', 'debug', 'info', 'warn', 'error', 'log'].includes(level)) {
554
+ return console.info;
555
+ }
556
+ return console[level];
557
+ })();
558
+ consoleLogFn(message);
559
+
560
+ // Note: this currently races with the VoiceEngine initialization,
561
+ // not all logs may get logged here early in the process
562
+ VoiceEngine.consoleLog(level, message);
563
+ }
564
+
565
+ console.log(`Initializing voice engine with audio subsystem: ${audioSubsystem}`);
566
+ VoiceEngine.platform = process.platform;
567
+ VoiceEngine.initialize({
568
+ audioSubsystem,
569
+ logLevel,
570
+ dataDirectory,
571
+ logDirectory,
572
+ useFakeVideoCapture,
573
+ useFileForFakeVideoCapture,
574
+ useFakeAudioCapture,
575
+ useFilesForFakeAudioCapture,
576
+ offloadAdmControls,
577
+ asyncVideoInputDeviceInit,
578
+ asyncClipsSourceDeinit,
579
+ });
580
+
581
+ module.exports = VoiceEngine;
manifest.json ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "files": [
3
+ "discord_voice.node",
4
+ "gpu_encoder_helper.exe",
5
+ "selfie_segmentation.tflite",
6
+ "selfie_segmentation_landscape.tflite",
7
+ "mediapipe.dll",
8
+ "package.json",
9
+ "index.js",
10
+ "manifest.json"
11
+ ]
12
+ }
mediapipe.dll ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b0eb7c4d2e4a211796ead7c0a78666940d4571c6c78cfa1eb9990a035ec107b3
3
+ size 8667576
package.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ {
2
+ "private": "true"
3
+ }
selfie_segmentation.tflite ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9ee168ec7c8f2a16c56fe8e1cfbc514974cbbb7e434051b455635f1bd1462f5c
3
+ size 249505
selfie_segmentation_landscape.tflite ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a77d03f4659b9f6b6c1f5106947bf40e99d7655094b6527f214ea7d451106edd
3
+ size 250145