This commit is contained in:
Халимов Рустам
2026-03-11 01:17:57 +03:00
parent ff740fb513
commit eea05ab26f

View File

@@ -188,100 +188,6 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi
setNoiseSuppression(false);
}, []);
// Setup common peer connection event handlers
const setupPeerHandlers = useCallback((pc: RTCPeerConnection) => {
pc.onicecandidate = (e) => {
if (e.candidate) {
const socket = getSocket();
socket?.emit('ice_candidate', {
targetUserId: targetUserIdRef.current,
candidate: e.candidate,
});
}
};
pc.ontrack = (e) => {
console.log('[ontrack] Received track:', e.track.kind, 'readyState:', e.track.readyState,
'enabled:', e.track.enabled, 'streams:', e.streams.length,
'muted:', e.track.muted);
// Always merge new tracks into the existing remote stream to avoid losing
// audio when a video-only screen share track arrives on a separate stream.
let stream: MediaStream;
if (remoteStreamRef.current) {
if (!remoteStreamRef.current.getTracks().includes(e.track)) {
remoteStreamRef.current.addTrack(e.track);
}
stream = remoteStreamRef.current;
} else if (e.streams[0]) {
stream = e.streams[0];
} else {
stream = new MediaStream([e.track]);
}
remoteStreamRef.current = stream;
// Check if remote has video tracks
const checkVideo = () => {
const videoTracks = stream.getVideoTracks();
// hasRemoteVideo stays true if we have ANY live video track.
const hasVideo = videoTracks.length > 0 && videoTracks.some(
t => t.readyState === 'live' && t.enabled && !t.muted
);
console.log('[checkVideo] videoTracks:', videoTracks.map(t =>
`${t.label} state=${t.readyState} enabled=${t.enabled} muted=${t.muted}`
), '→ hasRemoteVideo:', hasVideo);
setHasRemoteVideo(hasVideo);
// Fallback: if we detect a live video track but signalling hasn't updated us, switch to video mode
if (hasVideo && callType !== 'video') {
console.log('[checkVideo] Auto-switching to video mode');
setCallType('video');
}
};
// Listen for unmute/mute/ended on video tracks
const attachTrackListeners = (track: MediaStreamTrack) => {
if (track.kind !== 'video') return;
track.onunmute = checkVideo;
track.onmute = checkVideo;
track.onended = checkVideo;
};
stream.getVideoTracks().forEach(attachTrackListeners);
checkVideo();
if (remoteVideoRef.current) {
// Force re-assignment to ensure browser picks up new track
remoteVideoRef.current.srcObject = null;
remoteVideoRef.current.srcObject = stream;
}
if (remoteAudioRef.current) {
remoteAudioRef.current.srcObject = stream;
remoteAudioRef.current.play().catch(() => { });
}
// Track future additions/removals
stream.onaddtrack = (ev) => {
attachTrackListeners(ev.track);
checkVideo();
};
stream.onremovetrack = checkVideo;
// Also schedule a delayed check — some browsers delay unmute
setTimeout(checkVideo, 500);
setTimeout(checkVideo, 1500);
};
pc.onconnectionstatechange = () => {
if (callEndedRef.current) return;
if (pc.connectionState === 'disconnected' || pc.connectionState === 'failed') {
// eslint-disable-next-line @typescript-eslint/no-use-before-define
endCallSafe();
}
};
}, []);
// Schedule a tracked close timeout
const scheduleClose = useCallback((delay = 1500) => {
if (closeTimeoutRef.current) clearTimeout(closeTimeoutRef.current);
@@ -303,6 +209,93 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi
scheduleClose();
}, [cleanup, scheduleClose]);
// Setup common peer connection event handlers
const setupPeerHandlers = useCallback((pc: RTCPeerConnection) => {
pc.onicecandidate = (e) => {
if (e.candidate) {
const socket = getSocket();
socket?.emit('ice_candidate', {
targetUserId: targetUserIdRef.current,
candidate: e.candidate,
});
}
};
pc.ontrack = (event) => {
const track = event.track;
const streams = event.streams;
console.log(`[WebRTC] Received track: ${track.kind} state: ${track.readyState} streams: ${streams.length}`);
// Helper to update video UI state
const checkVideo = () => {
const stream = remoteStreamRef.current;
if (!stream) {
setHasRemoteVideo(false);
return;
}
const videoTracks = stream.getVideoTracks();
const hasVideo = videoTracks.length > 0 && videoTracks.some(
t => t.readyState === 'live' && t.enabled && !t.muted
);
console.log('[checkVideo] remote videoTracks:', videoTracks.map(t =>
`${t.label} state=${t.readyState} enabled=${t.enabled} muted=${t.muted}`
), '→ hasRemoteVideo:', hasVideo);
setHasRemoteVideo(hasVideo);
if (hasVideo && callType !== 'video') {
console.log('[checkVideo] Auto-switching to video mode');
setCallType('video');
}
};
// Always merge tracks into a single remote stream
if (!remoteStreamRef.current) {
remoteStreamRef.current = streams[0] || new MediaStream([track]);
} else if (streams[0]) {
streams[0].getTracks().forEach(t => {
if (!remoteStreamRef.current?.getTracks().includes(t)) {
remoteStreamRef.current?.addTrack(t);
}
});
} else if (!remoteStreamRef.current.getTracks().includes(track)) {
remoteStreamRef.current.addTrack(track);
}
const stream = remoteStreamRef.current;
// Bind to DOM elements
if (track.kind === 'video' && remoteVideoRef.current) {
remoteVideoRef.current.srcObject = stream;
remoteVideoRef.current.play().catch(err => console.warn('[WebRTC] video play failed:', err));
checkVideo();
} else if (track.kind === 'audio') {
if (remoteAudioRef?.current) {
remoteAudioRef.current.srcObject = stream;
remoteAudioRef.current.play().catch(err => console.warn('[WebRTC] audio play failed:', err));
} else if (remoteVideoRef.current) {
remoteVideoRef.current.srcObject = stream;
}
}
track.onunmute = checkVideo;
track.onmute = checkVideo;
track.onended = checkVideo;
// Extra checks for slow browsers
setTimeout(checkVideo, 500);
setTimeout(checkVideo, 1500);
};
pc.onconnectionstatechange = () => {
console.log('[WebRTC] Connection state:', pc.connectionState);
if (callEndedRef.current) return;
if (pc.connectionState === 'disconnected' || pc.connectionState === 'failed') {
endCallSafe();
}
};
}, [callType, endCallSafe]);
// Start outgoing call
const startCall = useCallback(async () => {
if (!targetUser) return;