Enhance SessionUsersPanel and UserCamera components by implementing local speaking state management with debounce for efficient updates. Refactor grid column calculation and participant filtering for improved performance. Adjust logging in useVoiceActivity and useWebRTC hooks to reduce console noise.
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
import { useMemo } from "react";
|
||||
import UserCamera from "./ui/UserCamera";
|
||||
import UserDevicesControls from "./ui/UserDevicesControls";
|
||||
import DraggableContainer from "./DraggableContainer";
|
||||
import { useWebRTC } from "../hooks/useWebRTC";
|
||||
import clsx from "clsx";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
interface SessionUsersPanelProps {
|
||||
roomId: string;
|
||||
@@ -28,47 +28,62 @@ function SessionUsersPanel({
|
||||
|
||||
const hasLocalStream = localStream !== null;
|
||||
|
||||
// Callback для отправки состояния speaking
|
||||
// State для хранения состояния speaking
|
||||
const [localSpeaking, setLocalSpeaking] = useState<boolean>(false);
|
||||
const lastSentSpeakingRef = useRef<boolean>(false);
|
||||
const speakingStateTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
// Callback для получения изменений состояния speaking от UserCamera
|
||||
const handleSpeakingChange = (isSpeaking: boolean) => {
|
||||
updateSpeakingState?.(isSpeaking);
|
||||
setLocalSpeaking(isSpeaking);
|
||||
};
|
||||
|
||||
// Вычисляем количество камер для grid - мемоизируем для избежания лишних вычислений
|
||||
const activeCamerasCount = useMemo(
|
||||
() =>
|
||||
(localStream ? 1 : 0) +
|
||||
participants.filter(
|
||||
(p) => p.stream != null && p.stream.getTracks().length > 0
|
||||
).length,
|
||||
[localStream, participants]
|
||||
);
|
||||
// useEffect для throttle и отправки состояния speaking через socket
|
||||
useEffect(() => {
|
||||
// Отправляем только если состояние действительно изменилось
|
||||
if (lastSentSpeakingRef.current === localSpeaking) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Очищаем предыдущий таймер
|
||||
if (speakingStateTimeoutRef.current) {
|
||||
clearTimeout(speakingStateTimeoutRef.current);
|
||||
}
|
||||
|
||||
// Отправляем состояние с задержкой (debounce 300ms)
|
||||
speakingStateTimeoutRef.current = setTimeout(() => {
|
||||
lastSentSpeakingRef.current = localSpeaking;
|
||||
updateSpeakingState?.(localSpeaking);
|
||||
speakingStateTimeoutRef.current = null;
|
||||
}, 300);
|
||||
|
||||
return () => {
|
||||
if (speakingStateTimeoutRef.current) {
|
||||
clearTimeout(speakingStateTimeoutRef.current);
|
||||
}
|
||||
};
|
||||
}, [localSpeaking, updateSpeakingState]);
|
||||
|
||||
// Вычисляем количество камер для grid
|
||||
const activeCamerasCount =
|
||||
(localStream ? 8 : 0) +
|
||||
participants.filter(
|
||||
(p) => p.stream != null && p.stream.getTracks().length > 0
|
||||
).length;
|
||||
// Определяем количество колонок в зависимости от количества камер
|
||||
// 1-2 камеры: 1 колонка (друг под другом), 3-4: 2 колонки, 5-9: 3 колонки, 10-16: 4 колонки, 17-25: 5 колонок
|
||||
const gridColumns = useMemo(() => {
|
||||
if (activeCamerasCount <= 2) return 1;
|
||||
if (activeCamerasCount <= 4) return 2;
|
||||
if (activeCamerasCount <= 9) return 3;
|
||||
if (activeCamerasCount <= 16) return 4;
|
||||
const getGridColumns = (count: number): number => {
|
||||
if (count <= 2) return 1;
|
||||
if (count <= 4) return 2;
|
||||
if (count <= 9) return 3;
|
||||
if (count <= 16) return 4;
|
||||
return 5;
|
||||
}, [activeCamerasCount]);
|
||||
};
|
||||
|
||||
const gridColumns = getGridColumns(activeCamerasCount);
|
||||
|
||||
// Вычисляем количество рядов для правильного расчета высоты
|
||||
const gridRows = useMemo(
|
||||
() => Math.ceil(activeCamerasCount / gridColumns),
|
||||
[activeCamerasCount, gridColumns]
|
||||
);
|
||||
|
||||
// Фильтруем участников с активными потоками - мемоизируем для избежания лишних фильтраций
|
||||
const activeParticipants = useMemo(
|
||||
() =>
|
||||
participants.filter(
|
||||
(participant) =>
|
||||
participant.stream != null &&
|
||||
participant.stream.getTracks().length > 0
|
||||
),
|
||||
[participants]
|
||||
);
|
||||
const gridRows = Math.ceil(activeCamerasCount / gridColumns);
|
||||
|
||||
// Рендерим камеры
|
||||
const camerasContent = (
|
||||
@@ -92,7 +107,13 @@ function SessionUsersPanel({
|
||||
)}
|
||||
|
||||
{/* Камеры удаленных участников - показываем только если есть поток с активными треками */}
|
||||
{activeParticipants.map((participant) => (
|
||||
{participants
|
||||
.filter(
|
||||
(participant) =>
|
||||
participant.stream != null &&
|
||||
participant.stream.getTracks().length > 0
|
||||
)
|
||||
.map((participant) => (
|
||||
<UserCamera
|
||||
key={participant.id}
|
||||
mode={mode}
|
||||
|
||||
@@ -73,8 +73,12 @@ export default function UserCamera({
|
||||
const isSpeaking = isLocal ? localSpeaking : remoteSpeaking || false;
|
||||
|
||||
// Отправляем изменения состояния для локального пользователя
|
||||
// Используем ref для отслеживания предыдущего состояния, чтобы избежать лишних вызовов
|
||||
const prevLocalSpeakingRef = useRef<boolean>(localSpeaking);
|
||||
|
||||
useEffect(() => {
|
||||
if (isLocal && onSpeakingChange) {
|
||||
if (isLocal && onSpeakingChange && prevLocalSpeakingRef.current !== localSpeaking) {
|
||||
prevLocalSpeakingRef.current = localSpeaking;
|
||||
onSpeakingChange(localSpeaking);
|
||||
}
|
||||
}, [isLocal, localSpeaking, onSpeakingChange]);
|
||||
@@ -83,16 +87,16 @@ export default function UserCamera({
|
||||
// isSpeaking уже учитывает threshold и debounce (1 сек)
|
||||
const ringOpacity = isSpeaking ? 1 : 0;
|
||||
|
||||
// Логируем для отладки
|
||||
useEffect(() => {
|
||||
console.log(
|
||||
`[${name}${
|
||||
isLocal ? " (local)" : ""
|
||||
}] isSpeaking: ${isSpeaking}, ringOpacity: ${ringOpacity.toFixed(
|
||||
2
|
||||
)}, isMuted: ${isMuted}`
|
||||
);
|
||||
}, [isSpeaking, ringOpacity, name, isMuted, isLocal]);
|
||||
// Логируем для отладки (отключено для снижения шума)
|
||||
// useEffect(() => {
|
||||
// console.log(
|
||||
// `[${name}${
|
||||
// isLocal ? " (local)" : ""
|
||||
// }] isSpeaking: ${isSpeaking}, ringOpacity: ${ringOpacity.toFixed(
|
||||
// 2
|
||||
// )}, isMuted: ${isMuted}`
|
||||
// );
|
||||
// }, [isSpeaking, ringOpacity, name, isMuted, isLocal]);
|
||||
|
||||
useEffect(() => {
|
||||
if (ref.current && mediaStream) {
|
||||
|
||||
@@ -132,9 +132,9 @@ export function useVoiceActivity(
|
||||
}
|
||||
}
|
||||
|
||||
// Логируем каждые 30 вызовов (~500ms при частоте 60 Hz)
|
||||
// Логируем каждые 180 вызовов (~3 секунды при частоте 60 Hz) - снижено для меньшего шума
|
||||
frameCount++;
|
||||
if (frameCount % 30 === 0) {
|
||||
if (frameCount % 180 === 0) {
|
||||
console.log(
|
||||
`[VoiceActivity] Level: ${audioLevel.toFixed(
|
||||
1
|
||||
|
||||
@@ -19,6 +19,14 @@ export const useWebRTC = (roomId?: string, autoJoin = false) => {
|
||||
const [isConnected, setIsConnected] = useState(false);
|
||||
const [isInitialized, setIsInitialized] = useState(false);
|
||||
|
||||
// Мониторинг изменений участников (отключено для снижения шума в консоли)
|
||||
// useEffect(() => {
|
||||
// console.log("[useWebRTC] Participants state updated:", participants.map(p => ({
|
||||
// id: p.id,
|
||||
// hasStream: !!p.stream,
|
||||
// })));
|
||||
// }, [participants]);
|
||||
|
||||
useEffect(() => {
|
||||
// Создаем сервис только один раз (синглтон)
|
||||
if (!webrtcServiceInstance) {
|
||||
@@ -63,11 +71,6 @@ export const useWebRTC = (roomId?: string, autoJoin = false) => {
|
||||
setParticipants((prev) => {
|
||||
const existing = prev.find((p) => p.id === participantId);
|
||||
if (existing) {
|
||||
// Если поток уже тот же самый, не обновляем
|
||||
if (existing.stream === stream) {
|
||||
console.log("[useWebRTC] Stream already set for:", participantId);
|
||||
return prev;
|
||||
}
|
||||
console.log("[useWebRTC] Updating stream for existing participant:", participantId);
|
||||
return prev.map((p) =>
|
||||
p.id === participantId ? { ...p, stream } : p
|
||||
@@ -97,43 +100,26 @@ export const useWebRTC = (roomId?: string, autoJoin = false) => {
|
||||
},
|
||||
onParticipantAudioToggle: (participantId, isEnabled) => {
|
||||
console.log(`[useWebRTC] Audio toggle for ${participantId}: ${isEnabled}`);
|
||||
setParticipants((prev) => {
|
||||
const participant = prev.find((p) => p.id === participantId);
|
||||
const newMutedState = !isEnabled;
|
||||
// Только обновляем, если значение действительно изменилось
|
||||
if (participant && participant.isMuted === newMutedState) {
|
||||
return prev;
|
||||
}
|
||||
return prev.map((p) =>
|
||||
p.id === participantId ? { ...p, isMuted: newMutedState } : p
|
||||
);
|
||||
});
|
||||
setParticipants((prev) =>
|
||||
prev.map((p) =>
|
||||
p.id === participantId ? { ...p, isMuted: !isEnabled } : p
|
||||
)
|
||||
);
|
||||
},
|
||||
onParticipantVideoToggle: (participantId, isEnabled) => {
|
||||
console.log(`[useWebRTC] Video toggle for ${participantId}: ${isEnabled}`);
|
||||
setParticipants((prev) => {
|
||||
const participant = prev.find((p) => p.id === participantId);
|
||||
const newVideoOffState = !isEnabled;
|
||||
// Только обновляем, если значение действительно изменилось
|
||||
if (participant && participant.isVideoOff === newVideoOffState) {
|
||||
return prev;
|
||||
}
|
||||
return prev.map((p) =>
|
||||
p.id === participantId ? { ...p, isVideoOff: newVideoOffState } : p
|
||||
);
|
||||
});
|
||||
setParticipants((prev) =>
|
||||
prev.map((p) =>
|
||||
p.id === participantId ? { ...p, isVideoOff: !isEnabled } : p
|
||||
)
|
||||
);
|
||||
},
|
||||
onParticipantSpeakingChange: (participantId, isSpeaking) => {
|
||||
setParticipants((prev) => {
|
||||
const participant = prev.find((p) => p.id === participantId);
|
||||
// Только обновляем, если значение действительно изменилось
|
||||
if (participant && participant.isSpeaking === isSpeaking) {
|
||||
return prev;
|
||||
}
|
||||
return prev.map((p) =>
|
||||
setParticipants((prev) =>
|
||||
prev.map((p) =>
|
||||
p.id === participantId ? { ...p, isSpeaking } : p
|
||||
);
|
||||
});
|
||||
)
|
||||
);
|
||||
},
|
||||
onChatMessage: (message) => {
|
||||
console.log("[useWebRTC] onChatMessage called:", message);
|
||||
|
||||
Reference in New Issue
Block a user