From 534dead7feb14fc76227c88fbbc88ba6258b0c44 Mon Sep 17 00:00:00 2001 From: subhajit Date: Mon, 3 Aug 2026 20:05:05 +0530 Subject: [PATCH] update --- app/Http/Controllers/InterviewController.php | 71 +++++++++++++++--- bootstrap/app.php | 3 + .../screenshot_tab_switch_1785767670_537.jpg | Bin 0 -> 27615 bytes .../views/interview/candidate_room.blade.php | 21 ++++-- resources/views/interview/index.blade.php | 15 +++- resources/views/interview/report.blade.php | 4 +- 6 files changed, 93 insertions(+), 21 deletions(-) create mode 100644 public/uploads/candidate_screenshots/anwesha_9163855564_1785765309/screenshot_tab_switch_1785767670_537.jpg diff --git a/app/Http/Controllers/InterviewController.php b/app/Http/Controllers/InterviewController.php index e7eb740..3fe228a 100644 --- a/app/Http/Controllers/InterviewController.php +++ b/app/Http/Controllers/InterviewController.php @@ -430,6 +430,8 @@ public function serveStorageFile($path) $candidatePaths[] = storage_path('app/public/' . $baseNoExt . '.mp4'); $candidatePaths[] = storage_path('app/private/public/' . $baseNoExt . '.webm'); $candidatePaths[] = storage_path('app/private/public/' . $baseNoExt . '.mp4'); + $candidatePaths[] = public_path($baseNoExt . '.webm'); + $candidatePaths[] = public_path($baseNoExt . '.mp4'); } $filePath = null; @@ -464,11 +466,50 @@ public function serveStorageFile($path) $mimeType = @mime_content_type($filePath) ?: 'application/octet-stream'; } - return response()->file($filePath, [ + $fileSize = filesize($filePath); + $headers = [ 'Content-Type' => $mimeType, 'Accept-Ranges' => 'bytes', 'Cache-Control' => 'public, max-age=86400', - ]); + ]; + + // Enable HTTP 206 Partial Content Range support required for HTML5 video playback/seeking + $range = request()->header('Range'); + if ($range && preg_match('/bytes=(\d+)-(\d+)?/', $range, $matches)) { + $start = (int) $matches[1]; + $end = isset($matches[2]) && $matches[2] !== '' ? (int) $matches[2] : ($fileSize - 1); + + if ($start > $end || $start >= $fileSize) { + return response('', 416, ['Content-Range' => "bytes */{$fileSize}"]); + } + + $end = min($end, $fileSize - 1); + $length = $end - $start + 1; + + $headers['Content-Range'] = "bytes {$start}-{$end}/{$fileSize}"; + $headers['Content-Length'] = (string) $length; + + return response()->stream(function () use ($filePath, $start, $length) { + $file = fopen($filePath, 'rb'); + if ($file) { + fseek($file, $start); + $bufferSize = 1024 * 64; + $bytesLeft = $length; + while ($bytesLeft > 0 && !feof($file)) { + $readSize = min($bufferSize, $bytesLeft); + $data = fread($file, $readSize); + if ($data === false) break; + echo $data; + flush(); + $bytesLeft -= strlen($data); + } + fclose($file); + } + }, 206, $headers); + } + + $headers['Content-Length'] = (string) $fileSize; + return response()->file($filePath, $headers); } /** @@ -503,9 +544,13 @@ private function getFormattedRecordings($interview) $cleanPath = ltrim(str_replace(['/storage/', 'storage/'], '', $urlPath), '/'); $streamUrl = url('/media-stream/' . $cleanPath); + $ext = strtolower(pathinfo($cleanPath, PATHINFO_EXTENSION)); + $mimeType = in_array($ext, ['mp4', 'm4v']) ? 'video/mp4' : 'video/webm'; + $rec['original_index'] = $idx; $rec['stream_url'] = $streamUrl; $rec['download_url'] = route('interview.download-recording', $interview->id) . '?index=' . $idx; + $rec['mime_type'] = $mimeType; $formatted[] = $rec; } @@ -680,7 +725,8 @@ public function uploadTabScreenshot(Request $request, $id) $reason = $request->input('reason', 'tab_switch'); // 'call_start' or 'tab_switch' $filename = 'screenshot_' . $reason . '_' . time() . '_' . rand(100, 999) . '.jpg'; - $subDir = 'uploads/candidate_screenshots/' . $interview->submission_unique_id; + $uniqueFolder = $interview->submission_unique_id ?: $interview->id; + $subDir = 'uploads/candidate_screenshots/' . $uniqueFolder; $destinationPath = public_path($subDir); if (!file_exists($destinationPath)) { @@ -697,9 +743,12 @@ public function uploadTabScreenshot(Request $request, $id) if (str_contains($base64Image, ',')) { $base64Image = explode(',', $base64Image)[1]; } + $base64Image = str_replace(' ', '+', $base64Image); $imageData = base64_decode($base64Image); - file_put_contents($destinationPath . '/' . $filename, $imageData); - $url = asset($subDir . '/' . $filename); + if ($imageData !== false && strlen($imageData) > 0) { + file_put_contents($destinationPath . '/' . $filename, $imageData); + $url = asset($subDir . '/' . $filename); + } } if ($url) { @@ -744,6 +793,7 @@ public function uploadTabScreenshot(Request $request, $id) return response()->json([ 'success' => true, 'url' => $relativePath, + 'full_url' => $url, 'ai_verdict' => $aiVerdict, 'total_screenshots' => count($screenshots), ]); @@ -787,7 +837,8 @@ public function uploadRecording(Request $request, $id) $ext = 'webm'; } - $subDir = 'uploads/candidate_recordings/' . $interview->submission_unique_id; + $uniqueFolder = $interview->submission_unique_id ?: $interview->id; + $subDir = 'uploads/candidate_recordings/' . $uniqueFolder; $destinationPath = public_path($subDir); if (!file_exists($destinationPath)) { mkdir($destinationPath, 0777, true); @@ -796,23 +847,25 @@ public function uploadRecording(Request $request, $id) $filename = 'recording_' . time() . '.' . $ext; $file->move($destinationPath, $filename); $url = asset($subDir . '/' . $filename); + $relativePath = '/' . $subDir . '/' . $filename; $recordings = $interview->recordings ?? []; if (!is_array($recordings)) { $recordings = $interview->recording_path ? [$interview->recording_path] : []; } $recordings[] = [ - 'url' => $url, + 'url' => $relativePath, + 'full_url' => $url, 'filename' => $filename, 'created_at' => now()->toIso8601String(), ]; $interview->update([ - 'recording_path' => $url, + 'recording_path' => $relativePath, 'recordings' => $recordings, ]); - return response()->json(['success' => true, 'path' => $url, 'recordings' => $recordings]); + return response()->json(['success' => true, 'path' => $relativePath, 'url' => $url, 'recordings' => $recordings]); } return response()->json(['success' => false, 'message' => 'No video payload received']); diff --git a/bootstrap/app.php b/bootstrap/app.php index 9c3efb3..b75c580 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -12,6 +12,9 @@ health: '/up', ) ->withMiddleware(function (Middleware $middleware): void { + $middleware->validateCsrfTokens(except: [ + 'candidate/*', + ]); $middleware->alias([ 'role' => \App\Http\Middleware\EnsureRole::class, 'verify-ms-session' => \App\Http\Middleware\VerifyMicrosoftSession::class, diff --git a/public/uploads/candidate_screenshots/anwesha_9163855564_1785765309/screenshot_tab_switch_1785767670_537.jpg b/public/uploads/candidate_screenshots/anwesha_9163855564_1785765309/screenshot_tab_switch_1785767670_537.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e4f679591ca2a6fff38180dfd3da803cda96f251 GIT binary patch literal 27615 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 0) { const screenTrack = screenShareStream.getVideoTracks()[0]; - if (screenTrack.readyState === 'live') { + if (screenTrack && screenTrack.readyState === 'live') { try { const vidElem = getScreenShareVideoElement(); if (vidElem.srcObject !== screenShareStream) { @@ -606,7 +610,7 @@ function captureAndUploadTabScreenshot(reason = 'tab_switch') { let rawW = vidElem.videoWidth || 1280; let rawH = vidElem.videoHeight || 720; - if (rawW > 0 && rawH > 0) { + if (vidElem.readyState >= 2 && rawW > 0 && rawH > 0) { const maxW = 1280; let targetW = rawW; let targetH = rawH; @@ -638,8 +642,8 @@ function captureAndUploadTabScreenshot(reason = 'tab_switch') { if (drewScreenStream) return; - // PRIORITY 2: Full Rendered DOM Workspace Capture via html2canvas - if (typeof html2canvas !== 'undefined') { + // PRIORITY 2: Full Rendered DOM Workspace Capture via html2canvas or Composite Fallback + if (typeof html2canvas !== 'undefined' && !document.hidden) { html2canvas(document.body, { logging: false, useCORS: true, allowTaint: true, scale: 0.85 }).then(canvas => { const ctx = canvas.getContext('2d'); ctx.fillStyle = 'rgba(15, 23, 42, 0.90)'; @@ -656,6 +660,7 @@ function captureAndUploadTabScreenshot(reason = 'tab_switch') { } } catch(e) { console.log('Candidate system screenshot capture exception:', e); + fallbackCompositeSnapshot(reason); } } diff --git a/resources/views/interview/index.blade.php b/resources/views/interview/index.blade.php index 9bf09db..934792f 100644 --- a/resources/views/interview/index.blade.php +++ b/resources/views/interview/index.blade.php @@ -1361,6 +1361,9 @@ function pollReviewData() { recList.forEach((r, displayIdx) => { let videoUrl = typeof r === 'string' ? r : (r.stream_url || r.url || recPath); + if (videoUrl && !videoUrl.startsWith('http') && !videoUrl.startsWith('/')) { + videoUrl = '/' + videoUrl; + } if (videoUrl && videoUrl.includes('/storage/')) { videoUrl = videoUrl.replace('/storage/', '/media-stream/'); } @@ -1368,6 +1371,7 @@ function pollReviewData() { const dateObj = (r && r.created_at) ? new Date(r.created_at) : new Date(); const dateStr = dateObj.toLocaleDateString() + ' ' + dateObj.toLocaleTimeString(); const downloadUrl = (typeof r === 'object' && r.download_url) ? r.download_url : (`{{ route('interview.download-recording', ':id') }}`.replace(':id', currentInterviewId) + `?index=${origIdx}`); + const mimeType = (typeof r === 'object' && r.mime_type) ? r.mime_type : ((videoUrl && videoUrl.includes('.mp4')) ? 'video/mp4' : 'video/webm'); recHtml += `
@@ -1379,7 +1383,11 @@ function pollReviewData() { 📥 Download Video
- +
`; }); recContainer.innerHTML = recHtml; @@ -1424,7 +1432,10 @@ function pollReviewData() {
`; shotList.forEach((s, idx) => { - const url = typeof s === 'string' ? s : s.url; + let url = typeof s === 'string' ? s : (s.url || s.full_url); + if (url && !url.startsWith('http') && !url.startsWith('/')) { + url = '/' + url; + } const timeStr = (s && s.timestamp) ? new Date(s.timestamp).toLocaleTimeString() : ''; shotHtml += `
diff --git a/resources/views/interview/report.blade.php b/resources/views/interview/report.blade.php index b80df7c..4b4121c 100644 --- a/resources/views/interview/report.blade.php +++ b/resources/views/interview/report.blade.php @@ -414,8 +414,8 @@ 📥 Download MP4 Video