fix
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user