fix: resolve LiveKit voice/video stability issues

- Guard Disconnected/ConnectionStateChanged event handlers against stale rooms:
  old room events no longer nuke the new room's state (root cause of buttons
  failing, mute getting stuck, DUPLICATE_IDENTITY cascades)
- Reset media state (isMuted/isCameraOn/isScreenSharing) on connect to prevent
  desync after reconnects
- Add voiceStates to WS ready payload so users see who's in voice on page load
- Wire VoiceControls buttons to check getActiveRoom() before SDK calls
- Guard ChannelSidebar against re-joining the same voice channel
- Switch LIVEKIT_URL to wss://nova.ddns.net/livekit for HTTPS secure context
  (required for getUserMedia in Safari)
This commit is contained in:
Jannis Braun
2026-02-18 07:52:25 +01:00
parent 5ef502f2e3
commit 4f65ea9c86
27 changed files with 573 additions and 143 deletions
@@ -10,13 +10,7 @@ import { useVoiceStore } from '../../stores/voiceStore';
import { Avatar } from '../ui/Avatar';
import { wsSend } from '../../hooks/useWebSocket';
interface ChannelSidebarProps {
onToggleMic: () => void;
onToggleCamera: () => void;
onToggleScreenShare: () => void;
}
export function ChannelSidebar({ onToggleMic, onToggleCamera, onToggleScreenShare }: ChannelSidebarProps) {
export function ChannelSidebar() {
const servers = useServerStore((s) => s.servers);
const currentServerId = useServerStore((s) => s.currentServerId);
const channels = useServerStore((s) => s.channels);
@@ -48,13 +42,14 @@ export function ChannelSidebar({ onToggleMic, onToggleCamera, onToggleScreenShar
};
const handleVoiceJoin = (channelId: string) => {
// Don't re-join the same channel — prevents duplicate LiveKit connections
if (currentVoiceChannelId === channelId) {
navigate(`/channels/${currentServerId}/${channelId}`);
return;
}
setCurrentVoiceChannel(channelId);
wsSend({ type: 'voice_join', channelId });
};
const handleVoiceDisconnect = () => {
setCurrentVoiceChannel(null);
wsSend({ type: 'voice_leave' });
navigate(`/channels/${currentServerId}/${channelId}`);
};
if (!server) {
@@ -262,15 +257,8 @@ export function ChannelSidebar({ onToggleMic, onToggleCamera, onToggleScreenShar
</div>
</div>
{/* Voice controls */}
{currentVoiceChannelId && (
<VoiceControls
onDisconnect={handleVoiceDisconnect}
onToggleMic={onToggleMic}
onToggleCamera={onToggleCamera}
onToggleScreenShare={onToggleScreenShare}
/>
)}
{/* Voice controls — VoiceControls reads state and calls LiveKit SDK directly */}
{currentVoiceChannelId && <VoiceControls />}
{/* User area */}
{user && (