Skip to content
This repository was archived by the owner on May 22, 2026. It is now read-only.
Open
61 changes: 53 additions & 8 deletions src/renderer/Voice.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import Peer from 'simple-peer';
import { ipcRenderer, remote } from 'electron';
import VAD from './vad';
import { ISettings } from './Settings';
import { validatePeerConfig } from './validatePeerConfig';

interface PeerConnections {
[peer: string]: Peer.Instance;
Expand Down Expand Up @@ -44,6 +45,26 @@ interface OtherDead {
[playerId: number]: boolean; // isTalking
}

interface ICEServer {
url: string,
username: string | undefined,
credential: string | undefined,
}

interface PeerConfig {
forceRelayOnly: Boolean,
stunServers: ICEServer[],
turnServers: ICEServer[]
}

const DEFAULT_ICE_CONFIG: RTCConfiguration = {
iceServers: [
{
urls: 'stun:stun.l.google.com:19302'
}
]
}

// function clamp(number: number, min: number, max: number): number {
// if (min > max) {
// let tmp = max;
Expand Down Expand Up @@ -154,15 +175,42 @@ export default function Voice() {
socket.on('connect', () => {
setConnected(true);
});

socket.on('disconnect', () => {
setConnected(false);
});

let iceConfig: RTCConfiguration = DEFAULT_ICE_CONFIG;
socket.on('peerConfig', (peerConfig: PeerConfig) => {
if (validatePeerConfig(peerConfig)) {
Comment thread
sjoerdthedev marked this conversation as resolved.
Outdated
if (peerConfig.forceRelayOnly && !peerConfig.turnServers) {
alert(`Server has forced relay mode enabled but provides no relay servers. Default config will be used.`);
return;
}

iceConfig = {
iceTransportPolicy: peerConfig.forceRelayOnly ? 'relay' : 'all',
iceServers: [...(peerConfig.stunServers || []), ...(peerConfig.turnServers || [])]
.map((server) => {
return {
urls: server.url,
username: server.username,
credential: server.credential
}
})
};
} else {
alert(`Server sent a malformed peer config. Default config will be used.${
validatePeerConfig.errors ?
` See errors below:\n${validatePeerConfig.errors.map(error => error.dataPath + ' ' + error.message).join('\n')}` : ``
}`);
}
})

// Initialize variables
let audioListener: any;
let audio: boolean | MediaTrackConstraints = true;


// Get microphone settings
if (settings.microphone.toLowerCase() !== 'default')
audio = { deviceId: settings.microphone };
Expand Down Expand Up @@ -235,14 +283,11 @@ export default function Voice() {
function createPeerConnection(peer: string, initiator: boolean) {
// console.log("Opening connection to ", peer, "Initiator: ", initiator);
const connection = new Peer({
stream, initiator, config: {
iceServers: [
{
'urls': 'stun:stun.l.google.com:19302'
}
]
}
stream,
initiator,
config: iceConfig
});

peerConnections[peer] = connection;

connection.on('stream', (stream: MediaStream) => {
Expand Down
19 changes: 19 additions & 0 deletions src/renderer/css/settings.css
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,23 @@ select {
.test-speakers {
width: fit-content;
margin: 5px auto;
}

.inline-icon {
display: inline;
margin-right: 8px;
vertical-align: middle;
}

.clickable, .clickable label {
cursor: pointer;
}

.collapsible-content-wrapper {
width: 100%;
text-align: center;
}

.collapsible-content {
padding-top: 12px;
}
33 changes: 33 additions & 0 deletions src/renderer/validatePeerConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import Ajv from 'ajv';

const ICE_SERVER_DEFINITION = {
type: 'array',
items: {
type: 'object',
properties: {
url: {
type: 'string',
format: 'uri'
},
username: {
type: 'string',
},
credential: {
type: 'string',
}
},
required: ['url']
}
}

export const validatePeerConfig = new Ajv({ format: 'full', allErrors: true }).compile({
type: 'object',
properties: {
forceRelayOnly: {
type: 'boolean'
},
stunServers: ICE_SERVER_DEFINITION,
turnServers: ICE_SERVER_DEFINITION,
},
required: ['forceRelayOnly']
});