This commit is contained in:
2025-05-15 05:20:01 +03:00
parent 623521f3b4
commit 21a05dd202
70 changed files with 4663 additions and 161 deletions

View File

@@ -0,0 +1,185 @@
// ~/components/webrtc-connection-manager.tsx
import { useEffect, useRef } from 'react'; // Removed useState
import { useActiveVoiceChannelStore } from '~/store/active-voice-channel';
import { useGatewayWebSocketStore } from '~/store/gateway-websocket';
import { useWebRTCStore } from '~/store/webrtc'; // Ensure WebRTCStatus is exported
export function WebRTCConnectionManager() {
console.log('WebRTC Manager: Mounting component.');
const {
serverId: activeServerId,
channelId: activeChannelId,
isVoiceActive,
} = useActiveVoiceChannelStore(state => ({
serverId: state.serverId,
channelId: state.channelId,
isVoiceActive: state.isVoiceActive,
}));
const gatewayStatus = useGatewayWebSocketStore((state) => state.status);
const {
joinVoiceChannel,
leaveVoiceChannel,
status: webRTCStatus,
currentChannelId: rtcCurrentChannelId,
} = useWebRTCStore((state) => ({
joinVoiceChannel: state.joinVoiceChannel,
leaveVoiceChannel: state.leaveVoiceChannel,
status: state.status,
currentChannelId: state.currentChannelId,
}));
// Use useRef for the stream to avoid re-triggering effect on set
const mediaStreamRef = useRef<MediaStream | null>(null);
// Use useRef for an operation lock to prevent re-entrancy
const operationLockRef = useRef<boolean>(false);
useEffect(() => {
console.log('WebRTC Manager: Effect triggered', {
activeServerId,
activeChannelId,
isVoiceActive,
gatewayStatus,
webRTCStatus,
rtcCurrentChannelId,
operationLock: operationLockRef.current,
});
const manageWebRTCConnection = async () => {
if (operationLockRef.current) {
console.debug('WebRTC Manager: Operation in progress, skipping.');
return;
}
const isConnectedToSomeChannel =
webRTCStatus !== "IDLE" &&
webRTCStatus !== "DISCONNECTED" &&
webRTCStatus !== "FAILED";
// --- Condition to JOIN/SWITCH voice ---
if (isVoiceActive && activeServerId && activeChannelId && gatewayStatus === 'CONNECTED') {
// Condition 1: Not connected at all, and want to join.
// Condition 2: Connected to a DIFFERENT channel, and want to switch.
const needsToJoinOrSwitch =
!isConnectedToSomeChannel || (rtcCurrentChannelId !== activeChannelId);
if (needsToJoinOrSwitch) {
operationLockRef.current = true;
console.log(`WebRTC Manager: Attempting to join/switch to ${activeServerId}/${activeChannelId}. Current RTC status: ${webRTCStatus}, current RTC channel: ${rtcCurrentChannelId}`);
// If currently connected to a different channel, leave it first.
if (isConnectedToSomeChannel && rtcCurrentChannelId && rtcCurrentChannelId !== activeChannelId) {
console.log(`WebRTC Manager: Leaving current channel ${rtcCurrentChannelId} before switching.`);
leaveVoiceChannel();
// leaveVoiceChannel will change webRTCStatus, triggering this effect again.
// The operationLock will be released when status becomes IDLE/DISCONNECTED.
// No 'return' here needed, let the status change from leave drive the next step.
// The lock is set, so next iteration won't try to join immediately.
// It will fall through to the lock release logic when state becomes IDLE.
} else { // Not connected or switching from a null/same channel (should be IDLE then)
let streamToUse = mediaStreamRef.current;
// Acquire media if we don't have a usable stream
if (!streamToUse || streamToUse.getTracks().every(t => t.readyState === 'ended')) {
if (streamToUse) { // Clean up old ended stream
streamToUse.getTracks().forEach(track => track.stop());
}
try {
console.log('WebRTC Manager: Acquiring new local media stream...');
streamToUse = await navigator.mediaDevices.getUserMedia({ audio: true, video: false });
mediaStreamRef.current = streamToUse;
} catch (err) {
console.error('WebRTC Manager: Failed to get user media:', err);
useWebRTCStore.setState({ status: "FAILED", lastError: 'Failed to get user media.' });
operationLockRef.current = false; // Release lock on failure
return; // Stop further processing for this run
}
}
if (streamToUse) {
console.log(`WebRTC Manager: Calling joinVoiceChannel for ${activeServerId}/${activeChannelId}`);
await joinVoiceChannel(activeServerId, activeChannelId, streamToUse);
// Don't release lock here immediately; let status changes from joinVoiceChannel
// (e.g., to CONNECTED or FAILED) handle releasing the lock.
} else {
console.error('WebRTC Manager: No media stream available to join channel.');
useWebRTCStore.setState({ status: "FAILED", lastError: 'Media stream unavailable.' });
operationLockRef.current = false; // Release lock
}
}
}
}
// --- Condition to LEAVE voice ---
else { // Not (isVoiceActive && activeServerId && activeChannelId && gatewayStatus === 'CONNECTED')
if (isConnectedToSomeChannel) {
operationLockRef.current = true;
console.log('WebRTC Manager: Conditions met to leave active voice channel. Leaving...', { isVoiceActive, activeServerId, gatewayStatus, webRTCStatus });
leaveVoiceChannel();
// Lock will be released when status becomes IDLE/DISCONNECTED.
}
}
// --- Manage operation lock based on final WebRTC state for this "cycle" ---
// This part is crucial: if an operation was started, the lock is only released
// when the WebRTC state settles into a terminal (IDLE, DISCONNECTED, FAILED) or successful (CONNECTED) state.
if (operationLockRef.current) { // Only if a lock was acquired in this effect run or previous
if (
webRTCStatus === "IDLE" ||
webRTCStatus === "DISCONNECTED" ||
webRTCStatus === "FAILED" ||
(webRTCStatus === "CONNECTED" && rtcCurrentChannelId === activeChannelId && isVoiceActive) // Successfully connected to desired channel
) {
// console.debug(`WebRTC Manager: Releasing operation lock. Status: ${webRTCStatus}`);
operationLockRef.current = false;
}
}
// --- Release media stream if no longer needed ---
// This should happen if WebRTC is inactive AND user doesn't want voice.
if (
mediaStreamRef.current &&
(webRTCStatus === "IDLE" || webRTCStatus === "DISCONNECTED" || webRTCStatus === "FAILED") &&
!isVoiceActive // Only if voice is explicitly deactivated
) {
console.log('WebRTC Manager: Releasing local media stream as WebRTC is inactive and voice is not desired.');
mediaStreamRef.current.getTracks().forEach(track => track.stop());
mediaStreamRef.current = null;
}
};
manageWebRTCConnection();
}, [
activeServerId,
activeChannelId,
isVoiceActive,
gatewayStatus,
webRTCStatus,
rtcCurrentChannelId,
joinVoiceChannel, // Stable from Zustand
leaveVoiceChannel, // Stable from Zustand
]);
// Cleanup on component unmount
useEffect(() => {
return () => {
console.log('WebRTC Manager: Unmounting component.');
// Ensure we attempt to leave if connection is active
const { status: currentRtcStatus, leaveVoiceChannel: finalLeave } = useWebRTCStore.getState();
if (currentRtcStatus !== "IDLE" && currentRtcStatus !== "DISCONNECTED") {
console.log('WebRTC Manager: Unmounting. Leaving active voice channel.');
finalLeave();
}
// Stop any tracks held by the ref
if (mediaStreamRef.current) {
console.log('WebRTC Manager: Unmounting. Stopping tracks from mediaStreamRef.');
mediaStreamRef.current.getTracks().forEach(track => track.stop());
mediaStreamRef.current = null;
}
};
}, []); // Empty dependency array for unmount cleanup only
return null;
}