Skip to content
This repository was archived by the owner on May 22, 2026. It is now read-only.
Open
60 changes: 53 additions & 7 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 '../common/ISettings';
import { validatePeerConfig } from './validatePeerConfig';

export interface ExtendedAudioElement extends HTMLAudioElement {
setSinkId: (sinkId: string) => Promise<void>;
Expand Down Expand Up @@ -43,6 +44,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 calculateVoiceAudio(state: AmongUsState, settings: ISettings, me: Player, other: Player, gain: GainNode, pan: PannerNode): void {
const audioContext = pan.context;
pan.positionZ.setValueAtTime(-0.5, audioContext.currentTime);
Expand Down Expand Up @@ -145,10 +166,38 @@ const Voice: React.FC = function () {
socket.on('connect', () => {
setConnected(true);
});

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

let iceConfig: RTCConfiguration = DEFAULT_ICE_CONFIG;
socket.on('peerConfig', (peerConfig: PeerConfig) => {
if (!validatePeerConfig(peerConfig)) {
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')}` : ``
}`);
return;
}

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

// Initialize variables
let audioListener: {
connect: () => void;
Expand Down Expand Up @@ -234,14 +283,11 @@ const Voice: React.FC = function () {
setConnect({ connect });
function createPeerConnection(peer: string, initiator: boolean) {
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
2 changes: 1 addition & 1 deletion src/renderer/css/settings.css
Original file line number Diff line number Diff line change
Expand Up @@ -107,4 +107,4 @@ select {
justify-content: center;
align-items: center;
width: 100vw;
}
}
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']
});