import React, { useEffect, useState } from 'react'; import { useVoiceStore } from '../../stores/voiceStore'; import { useUIStore } from '../../stores/uiStore'; import { getActiveRoom } from '../../hooks/useLiveKit'; import { wsSend } from '../../hooks/useWebSocket'; import { VideoQualityPopover } from './VideoQualityPopover'; import { VideoPresets, VideoPreset } from 'livekit-client'; const QUALITY_MAP: Record = { '1080p60': new VideoPreset(1920, 1080, 15_000_000, 60), '1080p': new VideoPreset(1920, 1080, 8_000_000, 30), '720p60': new VideoPreset(1280, 720, 8_000_000, 60), '720p': new VideoPreset(1280, 720, 5_000_000, 30), '540p': new VideoPreset(960, 540, 2_000_000, 30), '360p': new VideoPreset(640, 360, 1_000_000, 30), }; const btnBase = 'w-10 h-10 flex items-center justify-center rounded-full transition-colors'; const btnDefault = `${btnBase} bg-[#1e1f22] text-discord-text-secondary hover:bg-[#2b2d31] hover:text-discord-text-primary`; const btnActive = (color: string) => `${btnBase} bg-${color}/20 text-${color} hover:bg-${color}/30`; const btnGreen = `${btnBase} bg-[#1e1f22] text-discord-green hover:bg-[#2b2d31]`; export function VoiceControlBar() { const isMuted = useVoiceStore((s) => s.isMuted); const isDeafened = useVoiceStore((s) => s.isDeafened); const isCameraOn = useVoiceStore((s) => s.isCameraOn); const isScreenSharing = useVoiceStore((s) => s.isScreenSharing); const toggleMic = useVoiceStore((s) => s.toggleMic); const toggleDeafen = useVoiceStore((s) => s.toggleDeafen); const toggleCamera = useVoiceStore((s) => s.toggleCamera); const toggleScreenShare = useVoiceStore((s) => s.toggleScreenShare); const voiceChatOpen = useUIStore((s) => s.voiceChatOpen); const toggleVoiceChat = useUIStore((s) => s.toggleVoiceChat); const voiceFullscreen = useUIStore((s) => s.voiceFullscreen); const toggleVoiceFullscreen = useUIStore((s) => s.toggleVoiceFullscreen); const [qualityOpen, setQualityOpen] = useState(false); const handleMute = React.useCallback(async () => { const room = getActiveRoom(); if (room) { try { await room.localParticipant.setMicrophoneEnabled(isMuted); } catch (err) { console.error('[VoiceControlBar] Failed to toggle mic:', err); } } toggleMic(); }, [isMuted, toggleMic]); const handleDeafen = React.useCallback(async () => { const room = getActiveRoom(); if (room) { try { const willDeafen = !isDeafened; if (willDeafen) { await room.localParticipant.setMicrophoneEnabled(false); room.remoteParticipants.forEach((p) => p.setVolume(0)); if (!isMuted) toggleMic(); } else { const outputVolume = useVoiceStore.getState().outputVolume; room.remoteParticipants.forEach((p) => p.setVolume(outputVolume / 100)); await room.localParticipant.setMicrophoneEnabled(true); if (isMuted) toggleMic(); } } catch (err) { console.error('[VoiceControlBar] Failed to toggle deafen:', err); } } toggleDeafen(); }, [isDeafened, isMuted, toggleDeafen, toggleMic]); const handleCamera = async () => { const room = getActiveRoom(); if (!room) return; try { const willEnable = !isCameraOn; if (willEnable) { const videoQuality = useVoiceStore.getState().videoQuality; const preset = QUALITY_MAP[videoQuality]; if (preset) { await room.localParticipant.setCameraEnabled(true, { resolution: preset.resolution }, { videoEncoding: preset.encoding, simulcast: videoQuality === '1080p' || videoQuality === '720p' } ); } else { await room.localParticipant.setCameraEnabled(true); } } else { await room.localParticipant.setCameraEnabled(false); } toggleCamera(); } catch (err) { console.error('[VoiceControlBar] Failed to toggle camera:', err); } }; const handleScreenShare = async () => { const room = getActiveRoom(); if (!room) return; try { await room.localParticipant.setScreenShareEnabled(!isScreenSharing); toggleScreenShare(); } catch (err) { console.error('[VoiceControlBar] Failed to toggle screen share:', err); } }; const handleDisconnect = () => { wsSend({ type: 'voice_leave' }); useVoiceStore.getState().leaveVoice(); if (voiceFullscreen) { useUIStore.getState().setVoiceFullscreen(false); if (document.fullscreenElement) { document.exitFullscreen().catch(() => {}); } } }; const handleFullscreen = () => { if (!voiceFullscreen) { document.documentElement.requestFullscreen?.().catch(() => {}); } else { if (document.fullscreenElement) { document.exitFullscreen().catch(() => {}); } } toggleVoiceFullscreen(); }; // Keyboard shortcuts useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return; if (e.key === 'm' || e.key === 'M') { e.preventDefault(); handleMute(); } else if (e.key === 'd' || e.key === 'D') { e.preventDefault(); handleDeafen(); } else if (e.key === 'Escape' && voiceFullscreen) { useUIStore.getState().setVoiceFullscreen(false); } }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); }, [handleMute, handleDeafen, voiceFullscreen]); return (
{/* Mute */} {/* Deafen */} {/* Camera */} {/* Screen Share */} {/* Video Quality */}
setQualityOpen(false)} />
{/* Separator */}
{/* Chat Toggle */} {/* Fullscreen Toggle */} {/* Separator */}
{/* Disconnect */}
); }