From 1da4b26f5b1de288a3b08764e8c262b57f598eec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A5=D0=B0=D0=BB=D0=B8=D0=BC=D0=BE=D0=B2=20=D0=A0=D1=83?= =?UTF-8?q?=D1=81=D1=82=D0=B0=D0=BC?= Date: Wed, 11 Mar 2026 17:33:04 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A43?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/src/components/CallModal.tsx | 99 ++++++++++++++++++--------- 1 file changed, 68 insertions(+), 31 deletions(-) diff --git a/apps/web/src/components/CallModal.tsx b/apps/web/src/components/CallModal.tsx index ec06e13..af3c25c 100644 --- a/apps/web/src/components/CallModal.tsx +++ b/apps/web/src/components/CallModal.tsx @@ -214,20 +214,41 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi // Ensure remote stream is bound to video element when it becomes available useEffect(() => { - if (hasRemoteVideo && remoteVideoRef.current && remoteStreamRef.current) { - const video = remoteVideoRef.current; + if (callState === 'connected' && remoteVideoRef.current && remoteStreamRef.current) { + const video = remoteVideoRef.current as any; const stream = remoteStreamRef.current; - if (video.srcObject !== stream) { - console.log('[WebRTC] Effect: Binding stream to video element'); - video.srcObject = stream; - video.muted = false; - video.play().catch(e => { - if (e.name === 'NotAllowedError') setNeedsInteraction(true); - }); - } + const syncAndPlay = async (retryCount = 0) => { + if (video._playPromise) return; + + try { + if (video.srcObject !== stream) { + console.log('[WebRTC] Sync: Binding stream source'); + video.srcObject = stream; + } + video.muted = false; + + if (stream.getTracks().length > 0) { + video._playPromise = video.play(); + await video._playPromise; + video._playPromise = null; + setNeedsInteraction(false); + console.log('[WebRTC] Sync: Playback successful'); + } + } catch (e: any) { + video._playPromise = null; + if (e.name === 'AbortError' && retryCount < 10) { + console.log(`[WebRTC] Sync: Aborted, retrying (${retryCount + 1})...`); + setTimeout(() => syncAndPlay(retryCount + 1), 200); + } else if (e.name === 'NotAllowedError') { + setNeedsInteraction(true); + } + } + }; + + syncAndPlay(); } - }, [hasRemoteVideo]); + }, [hasRemoteVideo, callState]); // Setup common peer connection event handlers const setupPeerHandlers = useCallback((pc: RTCPeerConnection) => { @@ -246,11 +267,18 @@ 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 // 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 = streams[0] || new MediaStream(); + console.log('[WebRTC] Created new remoteStreamRef.current'); + } + + if (!remoteStreamRef.current.getTracks().includes(track)) { + console.log(`[WebRTC] Adding track ${track.kind} to remoteStreamRef`); remoteStreamRef.current.addTrack(track); + } else { + console.log(`[WebRTC] Track ${track.kind} already exists in remoteStreamRef`); } const stream = remoteStreamRef.current; @@ -289,40 +317,30 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi if (remoteVideoRef.current) { const video = remoteVideoRef.current as any; - const startPlayback = async () => { - // If a play request is already in progress, don't start a new one (as per Chrome docs) + const startPlayback = async (retryCount = 0) => { if (video._playPromise) { - console.log('[WebRTC] Play is already in progress, waiting...'); + console.log('[WebRTC] Play in progress, skipping startPlayback call'); return; } try { - // Small stabilization delay (100ms) - await new Promise(r => setTimeout(r, 100)); - if (video.srcObject !== stream) { - console.log('[WebRTC] Binding stream to video element'); video.srcObject = stream; } video.muted = false; - console.log('[WebRTC] Calling video.play()...'); + console.log('[WebRTC] ontrack: calling play()...'); video._playPromise = video.play(); - await video._playPromise; - - console.log('[WebRTC] Playback confirmed'); video._playPromise = null; setNeedsInteraction(false); } catch (e: any) { video._playPromise = null; - if (e.name === 'NotAllowedError') { - console.warn('[WebRTC] Autoplay blocked, showing UI'); + if (e.name === 'AbortError' && retryCount < 10) { + console.log(`[WebRTC] ontrack: Aborted, retrying (${retryCount + 1})...`); + setTimeout(() => startPlayback(retryCount + 1), 200); + } else if (e.name === 'NotAllowedError') { setNeedsInteraction(true); - } else if (e.name === 'AbortError') { - console.log('[WebRTC] Play aborted (likely concurrent track), will be retried automatically'); - } else { - console.error('[WebRTC] Fatal playback error:', e); } } }; @@ -1339,6 +1357,7 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi const onRenegotiate = async (data: { from: string; offer: RTCSessionDescriptionInit }) => { if (!peerRef.current || data.from !== targetUserIdRef.current) return; try { + console.log('[onRenegotiate] Received offer, setting remote description'); await peerRef.current.setRemoteDescription(new RTCSessionDescription(data.offer)); const answer = await peerRef.current.createAnswer(); await peerRef.current.setLocalDescription(answer); @@ -1346,6 +1365,22 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi targetUserId: targetUserIdRef.current, answer: peerRef.current.localDescription, }); + + // Nudge video check after renegotiation + setTimeout(() => { + console.log('[onRenegotiate] Nudging video check'); + // pc.ontrack should have fired by now, calling checkVideo via setHasRemoteVideo + // but we can trigger it manually if we find the stream + if (remoteStreamRef.current) { + const hasVideo = remoteStreamRef.current.getVideoTracks().some(t => t.readyState === 'live'); + console.log('[onRenegotiate] Manually found video tracks:', hasVideo); + if (hasVideo && !hasRemoteVideoRef.current) { + hasRemoteVideoRef.current = true; + setHasRemoteVideo(true); + setCallType('video'); + } + } + }, 1000); } catch (err) { console.error('Renegotiation error:', err); } @@ -1640,7 +1675,9 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi {displayName}
- Ожидание потока... + + {isScreenSharing ? 'Вы транслируете экран...' : 'Ожидание потока...'} +
)}