diff --git a/apps/web/src/components/CallModal.tsx b/apps/web/src/components/CallModal.tsx index 53467e5..2a011d8 100644 --- a/apps/web/src/components/CallModal.tsx +++ b/apps/web/src/components/CallModal.tsx @@ -226,13 +226,17 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi const streams = event.streams; console.log(`[WebRTC] Received track: ${track.kind} state: ${track.readyState} streams: ${streams.length}`); + // Ensure we have a stable remote stream object + if (!remoteStreamRef.current) { + remoteStreamRef.current = streams[0] || new MediaStream([track]); + } else if (!remoteStreamRef.current.getTracks().includes(track)) { + remoteStreamRef.current.addTrack(track); + } + + const stream = remoteStreamRef.current; + // 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 @@ -244,36 +248,35 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi setHasRemoteVideo(hasVideo); if (hasVideo && callType !== 'video') { - console.log('[checkVideo] Auto-switching to video mode'); setCallType('video'); } }; - // Ensure we have a stream object - if (!remoteStreamRef.current) { - remoteStreamRef.current = streams[0] || new MediaStream([track]); - } else if (!remoteStreamRef.current.getTracks().includes(track)) { - remoteStreamRef.current.addTrack(track); + // Force binding to the SINGLE stable video element + if (remoteVideoRef.current) { + if (remoteVideoRef.current.srcObject !== stream) { + console.log('[WebRTC] Binding stream to video element'); + remoteVideoRef.current.srcObject = stream; + } + // Try to play. If fails, it's usually autoplay policy + remoteVideoRef.current.play().catch(() => { + console.warn('[WebRTC] Autoplay blocked, waiting for interaction'); + // Important: we keep trying to play because users often click the UI + const retryPlay = () => { + remoteVideoRef.current?.play().then(() => { + console.log('[WebRTC] Video started after retry'); + }).catch(() => setTimeout(retryPlay, 2000)); + }; + retryPlay(); + }); } - const stream = remoteStreamRef.current; - - // Link to DOM elements - if (track.kind === 'video' && remoteVideoRef.current) { - remoteVideoRef.current.srcObject = stream; - // MUST be muted to autoplay in most browsers - remoteVideoRef.current.muted = true; - remoteVideoRef.current.play().catch(err => console.warn('[WebRTC] video play failed:', err)); - checkVideo(); - } - - if (track.kind === 'audio' || (track.kind === 'video' && !remoteAudioRef.current?.srcObject)) { - if (remoteAudioRef?.current) { - remoteAudioRef.current.srcObject = stream; - remoteAudioRef.current.play().catch(err => { - console.warn('[WebRTC] Audio play failed, user gesture needed:', err); - }); + // Handle audio via stable audio element + if (remoteAudioRef.current) { + if (remoteAudioRef.current.srcObject !== stream) { + remoteAudioRef.current.srcObject = stream; } + remoteAudioRef.current.play().catch(e => console.warn('[WebRTC] Audio play failed:', e)); } track.onunmute = checkVideo; @@ -1437,9 +1440,7 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi - {/* Hidden remote video/audio elements for background processing */} -