fix
This commit is contained in:
@@ -188,100 +188,6 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi
|
|||||||
setNoiseSuppression(false);
|
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
|
// Schedule a tracked close timeout
|
||||||
const scheduleClose = useCallback((delay = 1500) => {
|
const scheduleClose = useCallback((delay = 1500) => {
|
||||||
if (closeTimeoutRef.current) clearTimeout(closeTimeoutRef.current);
|
if (closeTimeoutRef.current) clearTimeout(closeTimeoutRef.current);
|
||||||
@@ -303,6 +209,93 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi
|
|||||||
scheduleClose();
|
scheduleClose();
|
||||||
}, [cleanup, 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
|
// Start outgoing call
|
||||||
const startCall = useCallback(async () => {
|
const startCall = useCallback(async () => {
|
||||||
if (!targetUser) return;
|
if (!targetUser) return;
|
||||||
|
|||||||
Reference in New Issue
Block a user