.
This commit is contained in:
@@ -1,185 +1,52 @@
|
||||
// ~/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
|
||||
import { useEffect, useRef } from "react";
|
||||
import { ConnectionState } from "~/lib/websocket/voice/types";
|
||||
import { useGatewayStore } from "~/stores/gateway-store";
|
||||
import { useVoiceStateStore } from "~/stores/voice-state-store";
|
||||
import { useWebRTCStore } from "~/stores/webrtc-store";
|
||||
|
||||
export function WebRTCConnectionManager() {
|
||||
console.log('WebRTC Manager: Mounting component.');
|
||||
const gateway = useGatewayStore();
|
||||
const voiceState = useVoiceStateStore();
|
||||
const webrtc = useWebRTCStore();
|
||||
|
||||
const {
|
||||
serverId: activeServerId,
|
||||
channelId: activeChannelId,
|
||||
isVoiceActive,
|
||||
} = useActiveVoiceChannelStore(state => ({
|
||||
serverId: state.serverId,
|
||||
channelId: state.channelId,
|
||||
isVoiceActive: state.isVoiceActive,
|
||||
}));
|
||||
const remoteStream = useWebRTCStore(state => state.remoteStream);
|
||||
const audioRef = useRef<HTMLAudioElement>(null)
|
||||
|
||||
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);
|
||||
if (audioRef.current) {
|
||||
audioRef.current.srcObject = remoteStream
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
console.log('WebRTC Manager: Effect triggered', {
|
||||
activeServerId,
|
||||
activeChannelId,
|
||||
isVoiceActive,
|
||||
gatewayStatus,
|
||||
webRTCStatus,
|
||||
rtcCurrentChannelId,
|
||||
operationLock: operationLockRef.current,
|
||||
const unsubscribe = gateway.onVoiceServerUpdate(async (event) => {
|
||||
await webrtc.connect(event.token);
|
||||
voiceState.joinVoiceChannel(event.serverId, event.channelId);
|
||||
|
||||
const stream = await navigator.mediaDevices.getUserMedia({
|
||||
audio: {
|
||||
noiseSuppression: false,
|
||||
},
|
||||
video: false
|
||||
});
|
||||
|
||||
webrtc.createOffer(stream);
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
voiceState.leaveVoiceChannel();
|
||||
unsubscribe();
|
||||
};
|
||||
}, []); // Empty dependency array for unmount cleanup only
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
useEffect(() => {
|
||||
if (webrtc.status === ConnectionState.DISCONNECTED) {
|
||||
voiceState.leaveVoiceChannel();
|
||||
}
|
||||
|
||||
}, [webrtc.status]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<audio autoPlay ref={audioRef} className="hidden" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user