Add docker setup and switch voice chat backend to use livekit

This commit is contained in:
2026-03-11 16:06:41 +01:00
parent b051a0a851
commit ae301db3bb
24 changed files with 1362 additions and 232 deletions

View File

@@ -284,8 +284,6 @@ let heartbeatAwaitingPong = false;
let reconnectInFlight = false;
let activeServerInstanceId: string | null = null;
let reloadScheduledForVersionMismatch = false;
let peerNegotiationReady = false;
let pendingSignalMessages: Array<Extract<IncomingMessage, { type: 'signal' }>> = [];
let peerListenGainByNickname = settings.loadPeerListenGains();
let audioLayers: AudioLayerState = {
voice: true,
@@ -322,10 +320,6 @@ const signaling = new SignalingClient(signalingUrl, handleSignalingStatus);
const peerManager = new PeerManager(
audio,
(targetId, payload) => {
signaling.send({ type: 'signal', targetId, ...payload });
},
() => mediaSession.getOutboundStream(),
updateStatus,
);
const mediaSession = new MediaSession({
@@ -1277,7 +1271,7 @@ async function checkMicPermission(): Promise<boolean> {
/** Starts local microphone capture and rebuilds the outbound track pipeline. */
async function setupLocalMedia(audioDeviceId = ''): Promise<void> {
await mediaSession.setupLocalMedia(audioDeviceId);
applyVoiceSendPermission();
authController.applyVoiceSendPermission();
}
/** Runs a short RMS sample to estimate and apply a usable microphone input gain. */
@@ -1497,25 +1491,20 @@ function disconnect(): void {
lastSubscriptionRefreshTileY = Math.round(state.player.y);
stopTeleportLoopAudio();
activeTeleport = null;
peerNegotiationReady = false;
pendingSignalMessages = [];
itemInteractionController.reset();
itemBehaviorRegistry.cleanup();
}
/** Starts peer negotiation only after welcome + media setup sequencing is complete. */
async function activatePeerNegotiation(): Promise<void> {
if (!state.running) return;
if (peerNegotiationReady) return;
peerNegotiationReady = true;
for (const peer of state.peers.values()) {
await peerManager.createOrGetPeer(peer.id, true, peer);
}
if (pendingSignalMessages.length === 0) return;
const queued = pendingSignalMessages;
pendingSignalMessages = [];
for (const signal of queued) {
await onAppMessage(signal);
/** Connects to the LiveKit room and ensures peers exist for roster members. */
async function connectLiveKit(url: string, token: string): Promise<void> {
try {
await peerManager.connectToRoom(url, token);
for (const peer of state.peers.values()) {
peerManager.ensurePeer(peer.id, peer);
}
} catch (error) {
console.error('LiveKit connect failed:', error);
updateStatus('LiveKit connection failed.');
}
}
@@ -1593,12 +1582,8 @@ const onAppMessage = createOnMessageHandler({
handleAdminUsersList,
handleAdminActionResult,
handleItemTransferTargets,
isPeerNegotiationReady: () => peerNegotiationReady,
enqueuePendingSignal: (message) => {
pendingSignalMessages.push(message);
if (pendingSignalMessages.length > 500) {
pendingSignalMessages.splice(0, pendingSignalMessages.length - 500);
}
connectToLiveKit: (url, token) => {
void connectLiveKit(url, token);
},
});
@@ -1670,14 +1655,12 @@ async function setupMediaAfterAuth(): Promise<void> {
const canProceed = await checkMicPermission();
if (!canProceed) {
setConnectionStatus('Microphone access is required.');
await activatePeerNegotiation();
return;
}
try {
await populateAudioDevices();
if (dom.audioInputSelect.options.length === 0) {
setConnectionStatus('No audio input device found. Open Audio setup or connect a microphone.');
await activatePeerNegotiation();
return;
}
const inputDeviceId = dom.audioInputSelect.value || mediaSession.getPreferredInputDeviceId();
@@ -1685,8 +1668,6 @@ async function setupMediaAfterAuth(): Promise<void> {
} catch (error) {
console.error(error);
setConnectionStatus(describeMediaError(error));
} finally {
await activatePeerNegotiation();
}
}