Enhance logging in SessionUsersPanel and ParticipantsPopup for debugging audio and video states. Update useWebRTC to initialize audio/video muted states from the service and add functionality to mute participants and disable their video. Adjust environment variables for server configuration.
This commit is contained in:
@@ -30,6 +30,13 @@ function SessionUsersPanel({
|
||||
|
||||
const hasLocalStream = localStream !== null;
|
||||
|
||||
// Логируем изменения hasLocalStream для отладки
|
||||
useEffect(() => {
|
||||
console.log(
|
||||
`[SessionUsersPanel] hasLocalStream changed to: ${hasLocalStream}`
|
||||
);
|
||||
}, [hasLocalStream]);
|
||||
|
||||
// State для хранения состояния speaking
|
||||
const [localSpeaking, setLocalSpeaking] = useState<boolean>(false);
|
||||
const lastSentSpeakingRef = useRef<boolean>(false);
|
||||
|
||||
@@ -8,7 +8,7 @@ import Avatar from "../ui/Avatar";
|
||||
import Button from "../ui/Button";
|
||||
import ShareFilledIcon from "../icons/ShareFilledIcon";
|
||||
import MicrophoneOffFilledIcon from "../icons/MicrophoneOffFilledIcon";
|
||||
import { Fragment, useRef } from "react";
|
||||
import { Fragment, useRef, useEffect } from "react";
|
||||
import DraggableContainer from "../DraggableContainer";
|
||||
import { useWebRTC } from "../../hooks/useWebRTC";
|
||||
import type { Participant } from "../../lib/webrtc";
|
||||
@@ -21,10 +21,23 @@ interface ParticipantsPopupProps {
|
||||
}
|
||||
|
||||
export default function ParticipantsPopup({ session }: ParticipantsPopupProps) {
|
||||
const { participants, currentUserId, localStream } = useWebRTC();
|
||||
const {
|
||||
participants,
|
||||
currentUserId,
|
||||
localStream,
|
||||
isAudioMuted,
|
||||
isVideoMuted,
|
||||
muteParticipant,
|
||||
disableParticipantVideo,
|
||||
} = useWebRTC();
|
||||
const { data: user } = useMe();
|
||||
const headerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Логируем каждый рендер компонента
|
||||
console.log(
|
||||
`[ParticipantsPopup RENDER] isAudioMuted=${isAudioMuted}, isVideoMuted=${isVideoMuted}`
|
||||
);
|
||||
|
||||
// Определяем, является ли текущий пользователь организатором
|
||||
const isOrganizer =
|
||||
!!(session.userId && user?.id === session.userId) ||
|
||||
@@ -36,10 +49,36 @@ export default function ParticipantsPopup({ session }: ParticipantsPopupProps) {
|
||||
id: currentUserId,
|
||||
stream: localStream || undefined,
|
||||
isLocal: true,
|
||||
isMuted: isAudioMuted,
|
||||
isVideoOff: isVideoMuted,
|
||||
},
|
||||
...participants,
|
||||
];
|
||||
|
||||
// Логируем создание массива для отладки
|
||||
console.log(
|
||||
`[ParticipantsPopup] allParticipants created with local user: isAudioMuted=${isAudioMuted}, isVideoMuted=${isVideoMuted}`
|
||||
);
|
||||
|
||||
// Логируем изменения состояния для отладки
|
||||
useEffect(() => {
|
||||
console.log(
|
||||
`[ParticipantsPopup] Local user state changed - isMuted: ${isAudioMuted}, isVideoOff: ${isVideoMuted}`
|
||||
);
|
||||
}, [isAudioMuted, isVideoMuted]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log(
|
||||
`[ParticipantsPopup] Participants:`,
|
||||
participants.map((p) => ({
|
||||
id: p.id.slice(0, 8),
|
||||
name: p.name,
|
||||
isMuted: p.isMuted,
|
||||
isVideoOff: p.isVideoOff,
|
||||
}))
|
||||
);
|
||||
}, [participants]);
|
||||
|
||||
return (
|
||||
<DraggableContainer
|
||||
enabled={window.innerWidth >= 640}
|
||||
@@ -63,6 +102,8 @@ export default function ParticipantsPopup({ session }: ParticipantsPopupProps) {
|
||||
isLocal={participant.isLocal || false}
|
||||
isOrganizer={isOrganizer}
|
||||
session={session}
|
||||
muteParticipant={muteParticipant}
|
||||
disableParticipantVideo={disableParticipantVideo}
|
||||
/>
|
||||
<hr className="w-full 2xl:h-[0.69vw] h-px border-[#F6F6F6] last:hidden" />
|
||||
</Fragment>
|
||||
@@ -99,23 +140,39 @@ function ParticipantItem({
|
||||
isLocal,
|
||||
isOrganizer,
|
||||
session,
|
||||
muteParticipant,
|
||||
disableParticipantVideo,
|
||||
}: {
|
||||
participant: Participant & { isLocal?: boolean };
|
||||
isLocal: boolean;
|
||||
isOrganizer: boolean;
|
||||
session: Session;
|
||||
muteParticipant: (participantId: string) => void;
|
||||
disableParticipantVideo: (participantId: string) => void;
|
||||
}) {
|
||||
const parentRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Проверяем наличие аудио/видео треков
|
||||
const hasAudio =
|
||||
participant.stream?.getAudioTracks().some((track) => track.enabled) ??
|
||||
false;
|
||||
const hasVideo =
|
||||
participant.stream?.getVideoTracks().some((track) => track.enabled) ??
|
||||
false;
|
||||
const isMuted = !hasAudio;
|
||||
const isVideoOff = !hasVideo;
|
||||
// Используем состояние из participant (для локального и удаленных участников)
|
||||
// Это состояние синхронизируется через Socket.IO для удаленных участников
|
||||
// и через хук useWebRTC для локального
|
||||
const isMuted = participant.isMuted ?? false;
|
||||
const isVideoOff = participant.isVideoOff ?? false;
|
||||
|
||||
// Логируем каждый рендер
|
||||
console.log(
|
||||
`[ParticipantItem RENDER] ${isLocal ? "Local" : "Remote"} ${
|
||||
participant.id.slice(0, 8)
|
||||
} - isMuted=${isMuted}, isVideoOff=${isVideoOff}, hasStream=${!!participant.stream}`
|
||||
);
|
||||
|
||||
// Логируем состояние участника для отладки
|
||||
useEffect(() => {
|
||||
console.log(
|
||||
`[ParticipantItem] ${isLocal ? "Local" : "Remote"} ${
|
||||
participant.id.slice(0, 8)
|
||||
} - isMuted: ${isMuted}, isVideoOff: ${isVideoOff}, participant.isMuted: ${participant.isMuted}, participant.isVideoOff: ${participant.isVideoOff}`
|
||||
);
|
||||
}, [isMuted, isVideoOff, isLocal, participant.id, participant.isMuted, participant.isVideoOff]);
|
||||
|
||||
// Определяем, является ли этот конкретный участник организатором сессии
|
||||
const isThisParticipantOrganizer =
|
||||
@@ -167,14 +224,20 @@ function ParticipantItem({
|
||||
label: "Выключить микрофон",
|
||||
onClick: () => {
|
||||
console.log("Mute participant:", participant.id);
|
||||
muteParticipant(participant.id);
|
||||
},
|
||||
// Disabled если микрофон уже выключен или нет потока
|
||||
disabled: isMuted || !participant.stream,
|
||||
},
|
||||
{
|
||||
icon: <VideoOffFilledIcon />,
|
||||
label: "Выключить камеру",
|
||||
onClick: () => {
|
||||
console.log("Turn off video:", participant.id);
|
||||
disableParticipantVideo(participant.id);
|
||||
},
|
||||
// Disabled если камера уже выключена или нет потока
|
||||
disabled: isVideoOff || !participant.stream,
|
||||
},
|
||||
{
|
||||
icon: <HandRaisedFilledIcon />,
|
||||
|
||||
@@ -40,7 +40,7 @@ function Button({
|
||||
variant === "secondary" &&
|
||||
"bg-[#FFFFFF] hover:bg-[#F0F0F0] active:text-[#7B60F3] active:bg-[#F3F1FD]",
|
||||
variant === "tertiary" &&
|
||||
"bg-[#FFFFFF] hover:bg-[#F0F0F0] text-[#7D7D7D] active:text-[#141414] active:bg-[#F3F3F3]",
|
||||
"bg-[#FFFFFF] hover:bg-[#F0F0F0] text-[#7D7D7D] active:text-[#141414] active:bg-[#F3F3F3] disabled:bg-transparent",
|
||||
variant === "critical" &&
|
||||
"text-[#FF4517] bg-[#FEF3F2] hover:bg-[#FEE4E2]",
|
||||
size === "large" &&
|
||||
|
||||
@@ -62,6 +62,27 @@ export default function UserCamera({
|
||||
// Для удаленных участников: если у локального пользователя есть разрешение на медиа - unmute, иначе mute (для autoplay)
|
||||
const [isAudioMuted, setIsAudioMuted] = useState(!hasLocalMediaPermission);
|
||||
|
||||
// Обновляем состояние muted при изменении hasLocalMediaPermission
|
||||
useEffect(() => {
|
||||
if (!isLocal) {
|
||||
// Для удаленных участников: если у локального нет разрешения - mute для autoplay
|
||||
setIsAudioMuted(!hasLocalMediaPermission);
|
||||
console.log(
|
||||
`[UserCamera] ${name} audio muted state updated to ${!hasLocalMediaPermission} (hasLocalMediaPermission: ${hasLocalMediaPermission})`
|
||||
);
|
||||
}
|
||||
}, [hasLocalMediaPermission, isLocal, name]);
|
||||
|
||||
// Явно обновляем атрибут muted у video элемента при изменении isAudioMuted
|
||||
useEffect(() => {
|
||||
if (ref.current && !isLocal) {
|
||||
ref.current.muted = isAudioMuted;
|
||||
console.log(
|
||||
`[UserCamera] ${name} video element muted attribute set to ${isAudioMuted}`
|
||||
);
|
||||
}
|
||||
}, [isAudioMuted, isLocal, name]);
|
||||
|
||||
// Детекция голосовой активности (только для локального пользователя)
|
||||
const { isSpeaking: isVoiceActive } = useVoiceActivity(
|
||||
isLocal ? mediaStream : null
|
||||
@@ -418,7 +439,7 @@ export default function UserCamera({
|
||||
|
||||
{mode === "full" && (
|
||||
<div className="2xl:px-[1.111vw] 2xl:py-[0.556vw] px-4 py-2 bg-[#141414]/25 backdrop-blur-[10px] 2xl:rounded-[1.111vw] rounded-2xl absolute 2xl:bottom-[1.111vw] bottom-4 left-1/2 -translate-x-1/2 z-10 flex 2xl:gap-[0.556vw] gap-2 items-center">
|
||||
<p className="text-white button-m font-medium">{name}</p>
|
||||
<p className="font-medium text-white button-m">{name}</p>
|
||||
{isMuted && (
|
||||
<div className="2xl:size-[1.111vw] size-4 text-white/50">
|
||||
<MicrophoneOffFilledIcon />
|
||||
|
||||
@@ -13,12 +13,35 @@ export const useWebRTC = (roomId?: string, autoJoin = false) => {
|
||||
const hasJoinedRoomRef = useRef(false);
|
||||
const [localStream, setLocalStream] = useState<MediaStream | null>(null);
|
||||
const [participants, setParticipants] = useState<Participant[]>([]);
|
||||
const [isAudioMuted, setIsAudioMuted] = useState(false);
|
||||
const [isVideoMuted, setIsVideoMuted] = useState(false);
|
||||
// Начальное состояние берем из сервиса, если он уже существует
|
||||
const [isAudioMuted, setIsAudioMuted] = useState(() => {
|
||||
const initialAudioMuted = webrtcServiceInstance ? webrtcServiceInstance.isAudioMuted() : true;
|
||||
console.log(`[useWebRTC INIT] Initial isAudioMuted: ${initialAudioMuted}`);
|
||||
return initialAudioMuted;
|
||||
});
|
||||
const [isVideoMuted, setIsVideoMuted] = useState(() => {
|
||||
const initialVideoMuted = webrtcServiceInstance ? webrtcServiceInstance.isVideoMuted() : true;
|
||||
console.log(`[useWebRTC INIT] Initial isVideoMuted: ${initialVideoMuted}`);
|
||||
return initialVideoMuted;
|
||||
});
|
||||
const [chatMessages, setChatMessages] = useState<ChatMessage[]>([]);
|
||||
const [isConnected, setIsConnected] = useState(false);
|
||||
const [isInitialized, setIsInitialized] = useState(false);
|
||||
|
||||
// Логируем начальное состояние при монтировании
|
||||
console.log(
|
||||
`[useWebRTC INIT] Component mounting with isAudioMuted=${isAudioMuted}, isVideoMuted=${isVideoMuted}`
|
||||
);
|
||||
|
||||
// Отслеживаем изменения isAudioMuted и isVideoMuted
|
||||
useEffect(() => {
|
||||
console.log(`[useWebRTC STATE] isAudioMuted changed to: ${isAudioMuted}`);
|
||||
}, [isAudioMuted]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log(`[useWebRTC STATE] isVideoMuted changed to: ${isVideoMuted}`);
|
||||
}, [isVideoMuted]);
|
||||
|
||||
// Мониторинг изменений участников (отключено для снижения шума в консоли)
|
||||
// useEffect(() => {
|
||||
// console.log("[useWebRTC] Participants state updated:", participants.map(p => ({
|
||||
@@ -38,6 +61,8 @@ export const useWebRTC = (roomId?: string, autoJoin = false) => {
|
||||
if (existingStream) {
|
||||
console.log("[useWebRTC] Initializing with existing local stream");
|
||||
setLocalStream(existingStream);
|
||||
// НЕ перезаписываем состояние - оно уже правильно установлено в useState
|
||||
// setIsAudioMuted и setIsVideoMuted уже получили правильные значения из webrtcServiceInstance
|
||||
setIsInitialized(true);
|
||||
}
|
||||
|
||||
@@ -64,6 +89,20 @@ export const useWebRTC = (roomId?: string, autoJoin = false) => {
|
||||
onLocalStreamReady: (stream) => {
|
||||
console.log("[useWebRTC] Local stream ready");
|
||||
setLocalStream(stream);
|
||||
// Когда ВПЕРВЫЕ получаем поток, устанавливаем аудио и видео как включенные
|
||||
// Используем callback форму setState чтобы проверить текущее состояние
|
||||
setIsAudioMuted((current) => {
|
||||
// Если текущее состояние true (выключено по умолчанию), устанавливаем false (включено)
|
||||
// Иначе оставляем как есть (пользователь мог уже изменить)
|
||||
const newState = current === true ? false : current;
|
||||
console.log(`[useWebRTC] onLocalStreamReady: isAudioMuted ${current} -> ${newState}`);
|
||||
return newState;
|
||||
});
|
||||
setIsVideoMuted((current) => {
|
||||
const newState = current === true ? false : current;
|
||||
console.log(`[useWebRTC] onLocalStreamReady: isVideoMuted ${current} -> ${newState}`);
|
||||
return newState;
|
||||
});
|
||||
setIsInitialized(true);
|
||||
},
|
||||
onRemoteStreamReady: (participantId, stream) => {
|
||||
@@ -98,6 +137,14 @@ export const useWebRTC = (roomId?: string, autoJoin = false) => {
|
||||
onParticipantLeft: (participantId) => {
|
||||
setParticipants((prev) => prev.filter((p) => p.id !== participantId));
|
||||
},
|
||||
onLocalAudioToggle: (isEnabled) => {
|
||||
console.log(`[useWebRTC] Local audio toggle callback: ${isEnabled}`);
|
||||
setIsAudioMuted(!isEnabled);
|
||||
},
|
||||
onLocalVideoToggle: (isEnabled) => {
|
||||
console.log(`[useWebRTC] Local video toggle callback: ${isEnabled}`);
|
||||
setIsVideoMuted(!isEnabled);
|
||||
},
|
||||
onParticipantAudioToggle: (participantId, isEnabled) => {
|
||||
console.log(`[useWebRTC] Audio toggle for ${participantId}: ${isEnabled}`);
|
||||
setParticipants((prev) =>
|
||||
@@ -149,11 +196,23 @@ export const useWebRTC = (roomId?: string, autoJoin = false) => {
|
||||
// считаем инициализацию завершенной
|
||||
if (stream === null) {
|
||||
console.log("[useWebRTC] Initialized without local stream (user denied permissions)");
|
||||
// Обновляем состояние muted на основе реального состояния сервиса
|
||||
const audioMuted = webrtcServiceInstance.isAudioMuted();
|
||||
const videoMuted = webrtcServiceInstance.isVideoMuted();
|
||||
console.log(`[useWebRTC] Setting isAudioMuted=${audioMuted}, isVideoMuted=${videoMuted}`);
|
||||
setIsAudioMuted(audioMuted);
|
||||
setIsVideoMuted(videoMuted);
|
||||
setIsInitialized(true);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("[useWebRTC] Initialization error:", error);
|
||||
// Даже при ошибке разрешаем продолжить
|
||||
// Обновляем состояние muted на основе реального состояния сервиса
|
||||
const audioMuted = webrtcServiceInstance.isAudioMuted();
|
||||
const videoMuted = webrtcServiceInstance.isVideoMuted();
|
||||
console.log(`[useWebRTC] Error case - Setting isAudioMuted=${audioMuted}, isVideoMuted=${videoMuted}`);
|
||||
setIsAudioMuted(audioMuted);
|
||||
setIsVideoMuted(videoMuted);
|
||||
setIsInitialized(true);
|
||||
} finally {
|
||||
isInitializing = false;
|
||||
@@ -193,13 +252,17 @@ export const useWebRTC = (roomId?: string, autoJoin = false) => {
|
||||
const toggleAudio = () => {
|
||||
if (!webrtcServiceInstance) return;
|
||||
const newState = webrtcServiceInstance.toggleAudio();
|
||||
console.log(`[useWebRTC] toggleAudio: newState=${newState}, setting isAudioMuted=${!newState}`);
|
||||
setIsAudioMuted(!newState);
|
||||
console.log(`[useWebRTC] setIsAudioMuted called with ${!newState}`);
|
||||
};
|
||||
|
||||
const toggleVideo = () => {
|
||||
if (!webrtcServiceInstance) return;
|
||||
const newState = webrtcServiceInstance.toggleVideo();
|
||||
console.log(`[useWebRTC] toggleVideo: newState=${newState}, setting isVideoMuted=${!newState}`);
|
||||
setIsVideoMuted(!newState);
|
||||
console.log(`[useWebRTC] setIsVideoMuted called with ${!newState}`);
|
||||
};
|
||||
|
||||
const sendMessage = (content: string, senderName?: string, isAuthenticated?: boolean) => {
|
||||
@@ -207,6 +270,16 @@ export const useWebRTC = (roomId?: string, autoJoin = false) => {
|
||||
webrtcServiceInstance.sendChatMessage(content, senderName, isAuthenticated);
|
||||
};
|
||||
|
||||
const muteParticipant = (participantId: string) => {
|
||||
if (!webrtcServiceInstance) return;
|
||||
webrtcServiceInstance.muteParticipant(participantId);
|
||||
};
|
||||
|
||||
const disableParticipantVideo = (participantId: string) => {
|
||||
if (!webrtcServiceInstance) return;
|
||||
webrtcServiceInstance.disableParticipantVideo(participantId);
|
||||
};
|
||||
|
||||
const joinRoom = async (roomId: string) => {
|
||||
if (!webrtcServiceInstance) return;
|
||||
await webrtcServiceInstance.joinRoom(roomId);
|
||||
@@ -246,5 +319,7 @@ export const useWebRTC = (roomId?: string, autoJoin = false) => {
|
||||
updateUserId,
|
||||
joinRoom,
|
||||
leaveRoom,
|
||||
muteParticipant,
|
||||
disableParticipantVideo,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -29,6 +29,8 @@ export interface WebRTCCallbacks {
|
||||
onRemoteStreamReady?: (participantId: string, stream: MediaStream) => void;
|
||||
onRoomParticipants?: (participantIds: string[]) => void;
|
||||
onChatMessage?: (message: ChatMessage) => void;
|
||||
onLocalAudioToggle?: (isEnabled: boolean) => void;
|
||||
onLocalVideoToggle?: (isEnabled: boolean) => void;
|
||||
onParticipantAudioToggle?: (
|
||||
participantId: string,
|
||||
isEnabled: boolean
|
||||
@@ -147,6 +149,22 @@ export function createWebRTCService(callbacks: WebRTCCallbacks = {}) {
|
||||
leaveRoom,
|
||||
sendChatMessage,
|
||||
updateSpeakingState,
|
||||
muteParticipant: (participantId: string) => {
|
||||
if (!state?.roomId) return;
|
||||
console.log(`[WebRTC] Muting participant ${participantId}`);
|
||||
state.socket.emit("mute-participant", {
|
||||
roomId: state.roomId,
|
||||
targetUserId: participantId,
|
||||
});
|
||||
},
|
||||
disableParticipantVideo: (participantId: string) => {
|
||||
if (!state?.roomId) return;
|
||||
console.log(`[WebRTC] Disabling video for participant ${participantId}`);
|
||||
state.socket.emit("disable-participant-video", {
|
||||
roomId: state.roomId,
|
||||
targetUserId: participantId,
|
||||
});
|
||||
},
|
||||
updateUserId: (newUserId: string) => {
|
||||
if (state) {
|
||||
console.log("[WebRTC] Updating userId from", state.userId, "to", newUserId);
|
||||
@@ -157,8 +175,16 @@ export function createWebRTCService(callbacks: WebRTCCallbacks = {}) {
|
||||
getCurrentUserId: () => state?.userId || "",
|
||||
getParticipants: () => Array.from(state?.participants.values() || []),
|
||||
getLocalStream: () => state?.localStream || null,
|
||||
isAudioMuted: () => (state ? !state.isAudioEnabled : true),
|
||||
isVideoMuted: () => (state ? !state.isVideoEnabled : true),
|
||||
isAudioMuted: () => {
|
||||
const result = state ? !state.isAudioEnabled : true;
|
||||
console.log(`[WebRTC] isAudioMuted() called: state.isAudioEnabled=${state?.isAudioEnabled}, returning ${result}`);
|
||||
return result;
|
||||
},
|
||||
isVideoMuted: () => {
|
||||
const result = state ? !state.isVideoEnabled : true;
|
||||
console.log(`[WebRTC] isVideoMuted() called: state.isVideoEnabled=${state?.isVideoEnabled}, returning ${result}`);
|
||||
return result;
|
||||
},
|
||||
hasLocalStream: () => state?.localStream !== null,
|
||||
addCallbacks: (newCallbacks: WebRTCCallbacks) => {
|
||||
if (state) {
|
||||
@@ -313,6 +339,22 @@ function setupSocketListeners() {
|
||||
callAllCallbacks("onError", new Error(error.message));
|
||||
});
|
||||
|
||||
// Обработка общих ошибок от сервера
|
||||
socket.on("error", (error: { message: string }) => {
|
||||
console.error("[WebRTC] Error received from server:", error);
|
||||
|
||||
// Показываем пользователю понятное сообщение
|
||||
if (error.message.includes("Unauthorized")) {
|
||||
alert("У вас нет прав для выполнения этого действия");
|
||||
} else if (error.message.includes("not found")) {
|
||||
alert("Сессия не найдена");
|
||||
} else {
|
||||
alert(`Ошибка: ${error.message}`);
|
||||
}
|
||||
|
||||
callAllCallbacks("onError", new Error(error.message));
|
||||
});
|
||||
|
||||
// Audio/Video toggle handlers
|
||||
socket.on("audio-toggle", ({ userId, isEnabled }: { userId: string; isEnabled: boolean }) => {
|
||||
console.log(`[WebRTC] Received audio-toggle from ${userId}: ${isEnabled}`);
|
||||
@@ -347,6 +389,38 @@ function setupSocketListeners() {
|
||||
}
|
||||
});
|
||||
|
||||
// Обработка принудительного выключения микрофона
|
||||
socket.on("force-mute-audio", () => {
|
||||
console.log("[WebRTC] Received force-mute-audio command from server");
|
||||
if (!state?.localStream) return;
|
||||
|
||||
const audioTracks = state.localStream.getAudioTracks();
|
||||
audioTracks.forEach((track) => {
|
||||
track.enabled = false;
|
||||
});
|
||||
state.isAudioEnabled = false;
|
||||
|
||||
// Уведомляем все компоненты об изменении
|
||||
callAllCallbacks("onLocalAudioToggle", false);
|
||||
console.log("[WebRTC] Audio forcefully muted by admin");
|
||||
});
|
||||
|
||||
// Обработка принудительного выключения камеры
|
||||
socket.on("force-disable-video", () => {
|
||||
console.log("[WebRTC] Received force-disable-video command from server");
|
||||
if (!state?.localStream) return;
|
||||
|
||||
const videoTracks = state.localStream.getVideoTracks();
|
||||
videoTracks.forEach((track) => {
|
||||
track.enabled = false;
|
||||
});
|
||||
state.isVideoEnabled = false;
|
||||
|
||||
// Уведомляем все компоненты об изменении
|
||||
callAllCallbacks("onLocalVideoToggle", false);
|
||||
console.log("[WebRTC] Video forcefully disabled by admin");
|
||||
});
|
||||
|
||||
console.log("Socket listeners set up complete");
|
||||
}
|
||||
|
||||
@@ -399,6 +473,11 @@ async function initializeLocalStream(): Promise<MediaStream | null> {
|
||||
console.warn("Продолжаем без локального медиа-потока:", errorMessage);
|
||||
callAllCallbacks("onError", new Error(errorMessage));
|
||||
|
||||
// Устанавливаем состояние аудио и видео в false, так как нет доступа к медиа
|
||||
state.isAudioEnabled = false;
|
||||
state.isVideoEnabled = false;
|
||||
console.log("[WebRTC] Set isAudioEnabled and isVideoEnabled to false (no media access)");
|
||||
|
||||
// Возвращаем null вместо выброса ошибки, чтобы пользователь мог продолжить
|
||||
return null;
|
||||
}
|
||||
@@ -408,6 +487,7 @@ async function joinRoom(roomId: string): Promise<void> {
|
||||
if (!state) throw new Error("WebRTC service not initialized");
|
||||
|
||||
console.log("Joining room:", roomId, "with user ID:", state.userId);
|
||||
console.log(`[WebRTC] Joining with isAudioEnabled=${state.isAudioEnabled}, isVideoEnabled=${state.isVideoEnabled}`);
|
||||
state.roomId = roomId;
|
||||
state.socket.emit("join-room", {
|
||||
roomId,
|
||||
@@ -805,6 +885,9 @@ function toggleAudio(): boolean {
|
||||
});
|
||||
state.isAudioEnabled = !state.isAudioEnabled;
|
||||
|
||||
// Уведомляем все компоненты об изменении локального аудио
|
||||
callAllCallbacks("onLocalAudioToggle", state.isAudioEnabled);
|
||||
|
||||
// Отправляем обновление состояния аудио всем участникам
|
||||
if (state.roomId) {
|
||||
state.socket.emit("audio-toggle", {
|
||||
@@ -835,6 +918,9 @@ function toggleVideo(): boolean {
|
||||
});
|
||||
state.isVideoEnabled = !state.isVideoEnabled;
|
||||
|
||||
// Уведомляем все компоненты об изменении локального видео
|
||||
callAllCallbacks("onLocalVideoToggle", state.isVideoEnabled);
|
||||
|
||||
// Отправляем обновление состояния видео всем участникам
|
||||
if (state.roomId) {
|
||||
state.socket.emit("video-toggle", {
|
||||
|
||||
+4
-4
@@ -1,6 +1,6 @@
|
||||
DATABASE_URL=postgres://postgres:v1sq3vD5faXL@194.26.138.94:5432/stream
|
||||
JWT_SECRET=b5cf2bd3894fb24191f13dc9dddaeecccc92d0ee298e7ee41c2d0aab51c28fa1
|
||||
# PORT=6000
|
||||
# SOCKET_PORT=6001
|
||||
PORT=3000
|
||||
SOCKET_PORT=3001
|
||||
PORT=6000
|
||||
SOCKET_PORT=6001
|
||||
# PORT=3000
|
||||
# SOCKET_PORT=3001
|
||||
+184
-1
@@ -59,6 +59,8 @@ interface User {
|
||||
id: string;
|
||||
roomId?: string;
|
||||
socketId: string;
|
||||
isAudioEnabled?: boolean;
|
||||
isVideoEnabled?: boolean;
|
||||
}
|
||||
|
||||
const rooms = new Map<string, Room>();
|
||||
@@ -119,17 +121,22 @@ io.on("connection", (socket) => {
|
||||
const room = rooms.get(roomId)!;
|
||||
room.participants.add(userId);
|
||||
|
||||
// Сохранить пользователя
|
||||
// Сохранить пользователя с состоянием аудио/видео
|
||||
users.set(userId, {
|
||||
id: userId,
|
||||
roomId,
|
||||
socketId: socket.id,
|
||||
isAudioEnabled: isAudioEnabled !== false,
|
||||
isVideoEnabled: isVideoEnabled !== false,
|
||||
});
|
||||
|
||||
console.log(
|
||||
`[WebRTC] Room ${roomId} now has participants:`,
|
||||
Array.from(room.participants)
|
||||
);
|
||||
console.log(
|
||||
`[WebRTC] User ${userId} media state: audio=${isAudioEnabled !== false}, video=${isVideoEnabled !== false}`
|
||||
);
|
||||
|
||||
// Уведомить других участников
|
||||
socket.to(roomId).emit("user-joined", userId);
|
||||
@@ -154,6 +161,24 @@ io.on("connection", (socket) => {
|
||||
participants
|
||||
);
|
||||
socket.emit("room-participants", participants);
|
||||
|
||||
// Отправить состояние аудио/видео существующих участников новому пользователю
|
||||
participants.forEach((participantId) => {
|
||||
const participant = users.get(participantId);
|
||||
if (participant) {
|
||||
console.log(
|
||||
`[WebRTC] Sending ${participantId} media state to ${userId}: audio=${participant.isAudioEnabled}, video=${participant.isVideoEnabled}`
|
||||
);
|
||||
socket.emit("audio-toggle", {
|
||||
userId: participantId,
|
||||
isEnabled: participant.isAudioEnabled !== false,
|
||||
});
|
||||
socket.emit("video-toggle", {
|
||||
userId: participantId,
|
||||
isEnabled: participant.isVideoEnabled !== false,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
@@ -225,6 +250,14 @@ io.on("connection", (socket) => {
|
||||
console.log(
|
||||
`[WebRTC] Audio toggle from ${userId} in room ${roomId}: ${isEnabled}`
|
||||
);
|
||||
|
||||
// Обновляем сохраненное состояние пользователя
|
||||
const user = users.get(userId);
|
||||
if (user) {
|
||||
user.isAudioEnabled = isEnabled;
|
||||
console.log(`[WebRTC] Updated ${userId} audio state to ${isEnabled}`);
|
||||
}
|
||||
|
||||
// Отправляем всем в комнате (кроме отправителя)
|
||||
socket.to(roomId).emit("audio-toggle", { userId, isEnabled });
|
||||
});
|
||||
@@ -233,6 +266,14 @@ io.on("connection", (socket) => {
|
||||
console.log(
|
||||
`[WebRTC] Video toggle from ${userId} in room ${roomId}: ${isEnabled}`
|
||||
);
|
||||
|
||||
// Обновляем сохраненное состояние пользователя
|
||||
const user = users.get(userId);
|
||||
if (user) {
|
||||
user.isVideoEnabled = isEnabled;
|
||||
console.log(`[WebRTC] Updated ${userId} video state to ${isEnabled}`);
|
||||
}
|
||||
|
||||
// Отправляем всем в комнате (кроме отправителя)
|
||||
socket.to(roomId).emit("video-toggle", { userId, isEnabled });
|
||||
});
|
||||
@@ -243,6 +284,148 @@ io.on("connection", (socket) => {
|
||||
socket.to(roomId).emit("speaking-state", { userId, isSpeaking });
|
||||
});
|
||||
|
||||
// Обработка команды выключения микрофона участника
|
||||
socket.on("mute-participant", async ({ roomId, targetUserId }) => {
|
||||
console.log(
|
||||
`[WebRTC] Mute participant request: ${targetUserId} in room ${roomId}`
|
||||
);
|
||||
|
||||
// Получаем информацию о пользователе, который отправил команду
|
||||
const requestingUser = findUserBySocketId(socket.id);
|
||||
if (!requestingUser) {
|
||||
console.warn(`[WebRTC] Unauthorized mute request from unknown socket ${socket.id}`);
|
||||
socket.emit("error", { message: "Unauthorized: user not found" });
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверяем, что пользователь находится в той же комнате
|
||||
if (requestingUser.roomId !== roomId) {
|
||||
console.warn(
|
||||
`[WebRTC] User ${requestingUser.id} tried to mute participant in room ${roomId}, but is in room ${requestingUser.roomId}`
|
||||
);
|
||||
socket.emit("error", { message: "Unauthorized: not in the same room" });
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверяем, что пользователь является организатором сессии
|
||||
try {
|
||||
const session = await serverSessionService.findById(roomId);
|
||||
if (!session) {
|
||||
console.warn(`[WebRTC] Session ${roomId} not found`);
|
||||
socket.emit("error", { message: "Session not found" });
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверяем, что запрашивающий пользователь - организатор
|
||||
// Организатор - это userId (для авторизованных) или guestId (для гостей)
|
||||
const isOrganizer =
|
||||
(session.userId && session.userId === requestingUser.id) ||
|
||||
(session.guestId && session.guestId === requestingUser.id);
|
||||
|
||||
if (!isOrganizer) {
|
||||
console.warn(
|
||||
`[WebRTC] User ${requestingUser.id} is not the organizer of session ${roomId}`
|
||||
);
|
||||
socket.emit("error", { message: "Unauthorized: only organizer can mute participants" });
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`[WebRTC] User ${requestingUser.id} is authorized as organizer`);
|
||||
} catch (error) {
|
||||
console.error(`[WebRTC] Error checking session organizer:`, error);
|
||||
socket.emit("error", { message: "Failed to verify permissions" });
|
||||
return;
|
||||
}
|
||||
|
||||
// Обновляем состояние участника
|
||||
const targetUser = users.get(targetUserId);
|
||||
if (targetUser) {
|
||||
targetUser.isAudioEnabled = false;
|
||||
console.log(`[WebRTC] Updated ${targetUserId} audio state to false`);
|
||||
}
|
||||
|
||||
// Отправляем команду конкретному участнику
|
||||
const targetSocketId = findSocketIdByUserId(targetUserId);
|
||||
if (targetSocketId) {
|
||||
io.to(targetSocketId).emit("force-mute-audio");
|
||||
console.log(`[WebRTC] Sent force-mute-audio to ${targetUserId}`);
|
||||
}
|
||||
|
||||
// Уведомляем всех в комнате об изменении состояния
|
||||
io.to(roomId).emit("audio-toggle", { userId: targetUserId, isEnabled: false });
|
||||
});
|
||||
|
||||
// Обработка команды выключения камеры участника
|
||||
socket.on("disable-participant-video", async ({ roomId, targetUserId }) => {
|
||||
console.log(
|
||||
`[WebRTC] Disable video request: ${targetUserId} in room ${roomId}`
|
||||
);
|
||||
|
||||
// Получаем информацию о пользователе, который отправил команду
|
||||
const requestingUser = findUserBySocketId(socket.id);
|
||||
if (!requestingUser) {
|
||||
console.warn(`[WebRTC] Unauthorized disable video request from unknown socket ${socket.id}`);
|
||||
socket.emit("error", { message: "Unauthorized: user not found" });
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверяем, что пользователь находится в той же комнате
|
||||
if (requestingUser.roomId !== roomId) {
|
||||
console.warn(
|
||||
`[WebRTC] User ${requestingUser.id} tried to disable video in room ${roomId}, but is in room ${requestingUser.roomId}`
|
||||
);
|
||||
socket.emit("error", { message: "Unauthorized: not in the same room" });
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверяем, что пользователь является организатором сессии
|
||||
try {
|
||||
const session = await serverSessionService.findById(roomId);
|
||||
if (!session) {
|
||||
console.warn(`[WebRTC] Session ${roomId} not found`);
|
||||
socket.emit("error", { message: "Session not found" });
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверяем, что запрашивающий пользователь - организатор
|
||||
// Организатор - это userId (для авторизованных) или guestId (для гостей)
|
||||
const isOrganizer =
|
||||
(session.userId && session.userId === requestingUser.id) ||
|
||||
(session.guestId && session.guestId === requestingUser.id);
|
||||
|
||||
if (!isOrganizer) {
|
||||
console.warn(
|
||||
`[WebRTC] User ${requestingUser.id} is not the organizer of session ${roomId}`
|
||||
);
|
||||
socket.emit("error", { message: "Unauthorized: only organizer can disable video" });
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`[WebRTC] User ${requestingUser.id} is authorized as organizer`);
|
||||
} catch (error) {
|
||||
console.error(`[WebRTC] Error checking session organizer:`, error);
|
||||
socket.emit("error", { message: "Failed to verify permissions" });
|
||||
return;
|
||||
}
|
||||
|
||||
// Обновляем состояние участника
|
||||
const targetUser = users.get(targetUserId);
|
||||
if (targetUser) {
|
||||
targetUser.isVideoEnabled = false;
|
||||
console.log(`[WebRTC] Updated ${targetUserId} video state to false`);
|
||||
}
|
||||
|
||||
// Отправляем команду конкретному участнику
|
||||
const targetSocketId = findSocketIdByUserId(targetUserId);
|
||||
if (targetSocketId) {
|
||||
io.to(targetSocketId).emit("force-disable-video");
|
||||
console.log(`[WebRTC] Sent force-disable-video to ${targetUserId}`);
|
||||
}
|
||||
|
||||
// Уведомляем всех в комнате об изменении состояния
|
||||
io.to(roomId).emit("video-toggle", { userId: targetUserId, isEnabled: false });
|
||||
});
|
||||
|
||||
// Обработка сообщений чата
|
||||
socket.on(
|
||||
"chat-message",
|
||||
|
||||
Reference in New Issue
Block a user