diff --git a/resources/views/interview/candidate_room.blade.php b/resources/views/interview/candidate_room.blade.php index 3b552b4..051bd1f 100644 --- a/resources/views/interview/candidate_room.blade.php +++ b/resources/views/interview/candidate_room.blade.php @@ -1069,14 +1069,25 @@ class CandRingtoneEngine { let candidateDeclinedOrLeftCall = false; let pendingOffer = null; let candRingTimer = null; + let latestCallStartedAt = null; + + function getCallSessionKey(timestamp) { + return 'handled_call_' + interviewId + '_' + (timestamp || latestCallStartedAt || 'active'); + } function triggerCandidateRing(offer = null, callStartedAt = null) { if (isCandidateCallConnected) return; if (latestCallStatus === 'ended' || latestCallStatus === 'idle') return; - if (candidateDeclinedOrLeftCall) { + if (callStartedAt) latestCallStartedAt = callStartedAt; + + const sessionKey = getCallSessionKey(callStartedAt); + const isHandled = sessionStorage.getItem(sessionKey) === 'handled'; + + if (isHandled || candidateDeclinedOrLeftCall) { showCandidateJoinOption(); return; } + if (offer) pendingOffer = offer; const badge = document.getElementById('call-status-badge'); @@ -1105,6 +1116,8 @@ function declineCandidateCall() { clearTimeout(candRingTimer); candRingTimer = null; } + const sessionKey = getCallSessionKey(); + sessionStorage.setItem(sessionKey, 'handled'); const modal = document.getElementById('candidate-incoming-modal'); if (modal) { modal.style.opacity = '0'; @@ -1120,6 +1133,8 @@ function stopCandidateRing() { clearTimeout(candRingTimer); candRingTimer = null; } + const sessionKey = getCallSessionKey(); + sessionStorage.setItem(sessionKey, 'handled'); const modal = document.getElementById('candidate-incoming-modal'); if (modal) { modal.style.opacity = '0'; @@ -1154,6 +1169,9 @@ function showCandidateJoinOption() { } async function acceptCandidateCall() { + const sessionKey = getCallSessionKey(); + sessionStorage.setItem(sessionKey, 'handled'); + candidateDeclinedOrLeftCall = false; candRingtone.stop(); if (candRingTimer) { @@ -1201,25 +1219,41 @@ function showCandidateJoinOption() { badge.style.color = '#34d399'; } - // Push candidate camera & microphone tracks to active WebRTC peer connection - if (localMediaStream && activeCallInstance && activeCallInstance.peerConnection) { - localMediaStream.getTracks().forEach(track => { - const senders = activeCallInstance.peerConnection.getSenders(); - const sender = senders.find(s => s.track && s.track.kind === track.kind); - if (sender) { - sender.replaceTrack(track).catch(e => {}); - } else { - try { activeCallInstance.peerConnection.addTrack(track, localMediaStream); } catch(e) {} - } - }); + // Dual 2-Way Connection: Answer incoming call AND establish connection to interviewer peer + const stream = localMediaStream || new MediaStream(); + + if (activeCallInstance) { + try { activeCallInstance.answer(stream); } catch(e) {} } + // Signal candidate ready via WebRTC BroadcastChannel + callSigChannel.postMessage({ type: 'candidate_ready', sender: 'candidate' }); + if (pendingOffer) { await handleInterviewerOffer(pendingOffer); pendingOffer = null; } - // Automatically capture candidate system screenshot on call start (if screenshot feature enabled) + // Call interviewer peer directly if interviewer registered + if (peerInstance) { + const interviewerPeerId = 'interviewer_' + interviewId; + try { + const outCall = peerInstance.call(interviewerPeerId, stream); + if (outCall) { + outCall.on('stream', remoteStream => { + const remoteVideo = document.getElementById('interviewer-video'); + const placeholder = document.getElementById('interviewer-video-placeholder'); + if (remoteVideo) { + remoteVideo.srcObject = remoteStream; + remoteVideo.play().catch(e => console.log('Remote video play error:', e)); + } + if (placeholder) placeholder.style.display = 'none'; + }); + } + } catch(e) {} + } + + // Automatically capture candidate system screenshot on call start (if enabled) if (!hasCapturedCallStartScreenshot && isTabSwitchScreenshotEnabled) { hasCapturedCallStartScreenshot = true; setTimeout(() => { @@ -1777,41 +1811,25 @@ function pollCandidateChat() { return; } - if (data.call_status === 'active') { - latestCallStatus = 'active'; - if (!isCandidateCallConnected) { - if (candidateDeclinedOrLeftCall) { - showCandidateJoinOption(); - } else { - triggerCandidateRing(null, data.call_started_at || null); - } + if (data.warnings && Array.isArray(data.warnings)) { + if (data.warnings.length === 0) { + chatHistory.innerHTML = '