diff --git a/app/Http/Controllers/InterviewController.php b/app/Http/Controllers/InterviewController.php index a5cd4ef..45ca34d 100644 --- a/app/Http/Controllers/InterviewController.php +++ b/app/Http/Controllers/InterviewController.php @@ -533,6 +533,14 @@ public function getActiveCalls(Request $request) $activeCalls = []; foreach ($accessible as $interview) { + // Auto-terminate call session if interview timeline has expired + if ($interview->isExpired()) { + if ($interview->call_status === 'active') { + $interview->update(['call_status' => 'ended']); + } + continue; + } + if ($interview->call_status === 'active') { $starterName = 'Panelist'; if ($interview->call_started_by) { @@ -570,6 +578,14 @@ public function getPollData($id) ->where('id', $id) ->orWhere('submission_unique_id', $id) ->firstOrFail(); + + // Enforce expiration: ended call status if timeline is expired + if ($interview->isExpired()) { + if ($interview->call_status === 'active') { + $interview->update(['call_status' => 'ended']); + } + } + $formattedRecordings = $this->getFormattedRecordings($interview); $starterName = $interview->callStarter ? $interview->callStarter->name : null; @@ -580,7 +596,7 @@ public function getPollData($id) return response()->json([ 'status' => $interview->status, - 'call_status' => $interview->call_status ?? 'idle', + 'call_status' => $interview->isExpired() ? 'ended' : ($interview->call_status ?? 'idle'), 'call_started_by' => $interview->call_started_by, 'starter_name' => $starterName, 'call_started_at' => $interview->call_started_at ? $interview->call_started_at->toIso8601String() : null, diff --git a/resources/views/interview/index.blade.php b/resources/views/interview/index.blade.php index 361e7e0..80d5350 100644 --- a/resources/views/interview/index.blade.php +++ b/resources/views/interview/index.blade.php @@ -1029,17 +1029,24 @@ class CallRingtoneEngine { const endedCallIds = new Set(); const currentUserId = {{ Auth::id() }}; const globalCallChannel = new BroadcastChannel('global_call_alerts'); + const adminPageLoadTimestamp = Date.now(); - function triggerIncomingCallRing(callData) { + function triggerIncomingCallRing(callData, isRealtimeBroadcast = false) { if (endedCallIds.has(callData.id) || dismissedCallIds.has(callData.id)) return; if (iAmTheCaller && currentInterviewId == callData.id) return; if (interviewerLocalStream && currentInterviewId == callData.id) return; - // Check ring 10-second threshold based on call_started_at + // Only play ringtone for real-time fresh calls initiated within the last 8 seconds if (callData.call_started_at) { const startedTime = new Date(callData.call_started_at).getTime(); const elapsedMs = Date.now() - startedTime; - if (elapsedMs > 10000) return; + if (elapsedMs > 8000 && !isRealtimeBroadcast) { + dismissedCallIds.add(callData.id); + return; + } + } else if (!isRealtimeBroadcast) { + dismissedCallIds.add(callData.id); + return; } activeIncomingCall = callData; @@ -1048,13 +1055,13 @@ function triggerIncomingCallRing(callData) { document.getElementById('incoming-call-modal').classList.add('active'); callRingtone.start(); - // Set 10-second auto-stop ring timeout + // Set 8-second auto-stop ring timeout if (ringTimeoutTimer) clearTimeout(ringTimeoutTimer); - let remainingMs = 10000; + let remainingMs = 8000; if (callData.call_started_at) { const startedTime = new Date(callData.call_started_at).getTime(); const elapsedMs = Date.now() - startedTime; - remainingMs = Math.max(1000, 10000 - elapsedMs); + remainingMs = Math.max(1000, 8000 - elapsedMs); } ringTimeoutTimer = setTimeout(() => { @@ -1073,7 +1080,7 @@ function triggerIncomingCallRing(callData) { submission_unique_id: data.submission_unique_id, starter_name: data.starter_name, call_started_at: data.call_started_at || new Date().toISOString() - }); + }, true); } else if (data.type === 'call_ended') { endedCallIds.add(data.interview_id); dismissedCallIds.add(data.interview_id); @@ -1111,8 +1118,16 @@ function pollGlobalActiveCalls() { }); if (incoming) { + // Check if call was started before page refresh + const startedTime = incoming.call_started_at ? new Date(incoming.call_started_at).getTime() : 0; + if (startedTime > 0 && startedTime < (adminPageLoadTimestamp - 4000)) { + // Ongoing call from before page reload: silent status update without ringtone + dismissedCallIds.add(incoming.id); + return; + } + if (!activeIncomingCall || activeIncomingCall.id !== incoming.id) { - triggerIncomingCallRing(incoming); + triggerIncomingCallRing(incoming, false); } } else if (activeIncomingCall) { declineIncomingCall(true); @@ -1121,8 +1136,8 @@ function pollGlobalActiveCalls() { .catch(() => {}); } - // Poll for active calls every 1 second for instant ring notification - setInterval(pollGlobalActiveCalls, 1000); + // Poll for active calls every 1.5 seconds + setInterval(pollGlobalActiveCalls, 1500); function acceptIncomingCall() { callRingtone.stop();