Files
backspace/packages/web/src/utils/voiceActions.ts
T
Jannis Braun ba7b975bc8 feat: add federatedCallId and callOrigin to client call routing
Enable federated DM calls to route accept/reject/end through the correct
WebSocket connection using callOrigin, and include federatedCallId in all
dm_call payloads for server-side FederatedCallEntry lookup.
2026-04-08 03:26:48 +02:00

115 lines
3.6 KiB
TypeScript

import { useVoiceStore } from '../stores/voiceStore';
import { useUIStore } from '../stores/uiStore';
import { getActiveRoom } from '../hooks/useLiveKit';
import { wsSend } from '../hooks/useWebSocket';
import { getChannelOrigin } from '../stores/spaceStore';
import { broadcastVoiceStatus, broadcastDeafenViaLiveKit } from './voice';
import { CAMERA_PRESET, startScreenShare, stopScreenShare } from './screenShare';
/**
* Toggle mute. Respects space-mute/deafen guards.
* Extracted from VoiceControlBar so keybinds and buttons share the same logic.
*/
export function handleMuteAction(isSpaceMuted: boolean, isSpaceDeafened: boolean): void {
if (isSpaceMuted || isSpaceDeafened) return;
const wasDeafened = useVoiceStore.getState().isDeafened;
useVoiceStore.getState().toggleMic();
broadcastVoiceStatus();
if (wasDeafened && !useVoiceStore.getState().isDeafened) {
broadcastDeafenViaLiveKit();
}
}
/**
* Toggle deafen. Respects space-deafen guard.
*/
export function handleDeafenAction(isSpaceDeafened: boolean): void {
if (isSpaceDeafened) return;
useVoiceStore.getState().toggleDeafen();
broadcastVoiceStatus();
broadcastDeafenViaLiveKit();
}
/**
* Toggle camera. Requires LiveKit room.
*/
export async function handleCameraAction(): Promise<void> {
const room = getActiveRoom();
if (!room) return;
const isCameraOn = useVoiceStore.getState().isCameraOn;
try {
const willEnable = !isCameraOn;
if (willEnable) {
await room.localParticipant.setCameraEnabled(true,
{ resolution: CAMERA_PRESET.resolution },
{
videoCodec: CAMERA_PRESET.codec,
videoEncoding: CAMERA_PRESET.encoding,
simulcast: true,
}
);
} else {
await room.localParticipant.setCameraEnabled(false);
}
useVoiceStore.getState().toggleCamera();
broadcastVoiceStatus();
} catch (err) {
console.error('[voiceActions] Failed to toggle camera:', err);
}
}
/**
* Toggle screen share. Requires LiveKit room.
* Note: startScreenShare/stopScreenShare manage voiceStore.isScreenSharing internally.
* Do NOT call toggleScreenShare() here — it would double-flip the state.
*/
export async function handleScreenShareAction(): Promise<void> {
const room = getActiveRoom();
if (!room) return;
const isScreenSharing = useVoiceStore.getState().isScreenSharing;
try {
if (!isScreenSharing) {
const started = await startScreenShare(room);
if (started) broadcastVoiceStatus();
} else {
await stopScreenShare(room);
broadcastVoiceStatus();
}
} catch (err) {
console.error('[voiceActions] Failed to toggle screen share:', err);
}
}
/**
* Disconnect from voice. Handles DM call teardown and fullscreen exit.
*/
export function handleDisconnectAction(): void {
const voice = useVoiceStore.getState();
const { activeDmCall, currentVoiceChannelId, disconnectFn } = voice;
if (activeDmCall) {
const origin = voice.callOrigin || getChannelOrigin(activeDmCall.dmChannelId);
wsSend(
{ type: 'dm_call_end', dmChannelId: activeDmCall.dmChannelId, federatedCallId: voice.federatedCallId },
origin
);
voice.setActiveDmCall(null);
} else if (currentVoiceChannelId) {
const origin = getChannelOrigin(currentVoiceChannelId);
wsSend({ type: 'voice_leave' }, origin);
voice.leaveVoice();
}
// Tear down the LiveKit connection
if (disconnectFn) disconnectFn();
// Exit fullscreen if active
const voiceFullscreen = useUIStore.getState().voiceFullscreen;
if (voiceFullscreen) {
useUIStore.getState().setVoiceFullscreen(false);
if (document.fullscreenElement) {
document.exitFullscreen().catch(() => {});
}
}
}