let ws; const videoPlayer = document.getElementById('videoPlayer'); const videoOverlay = document.getElementById('videoOverlay'); const progressContainer = document.getElementById('progressContainer'); const progressFill = document.getElementById('progressFill'); const timer = document.getElementById('timer'); const loadingSpinner = document.getElementById('loadingSpinner'); const loadingStatusText = document.getElementById('loadingStatusText'); // new element let backendDuration = null; // Duration provided by backend console.log('Renderer loaded. DOM elements initialized.'); // Console log passthrough to Electron main if (window.require) { const { ipcRenderer } = window.require('electron'); ['log', 'error', 'warn', 'info'].forEach(level => { const orig = console[level]; console[level] = function(...args) { ipcRenderer.send('ui-log', { level, args }); orig.apply(console, args); }; }); console.log('Electron ipcRenderer logging enabled.'); } // Always muted for security videoPlayer.muted = true; console.log('Video player muted.'); function connectWebSocket() { console.log('Connecting to WebSocket...'); ws = new WebSocket('ws://localhost:8001'); ws.onmessage = (event) => { console.log('WebSocket message received:', event.data); try { const message = JSON.parse(event.data); console.log('Parsed message:', message); if (message.type === 'play_video') { console.log('play_video command received:', message); playVideo( message.url, message.location || "center", message.size || 5, message.duration ?? null ); } else if (message.type === 'stop_video') { console.log('stop_video command received.'); stopVideo(); } else if (message.type === 'status_update') { console.log('status_update received:', message.status); updateLoadingStatus(message.status); } } catch (error) { showError('Error parsing message from server.'); console.error('Error parsing message:', error); } }; ws.onclose = () => { console.warn('WebSocket closed. Retrying...'); showError('Lost connection to WebSocket server. Retrying...'); setTimeout(connectWebSocket, 3000); }; ws.onerror = (err) => { console.error('WebSocket error:', err); showError('WebSocket connection error. Is the server running?'); }; } function updateLoadingStatus(status) { console.log('Updating loading status:', status); if (loadingStatusText) { loadingStatusText.textContent = status; loadingSpinner.style.display = 'flex'; videoPlayer.style.display = 'none'; progressContainer.style.display = 'none'; videoOverlay.classList.remove('hidden'); } } function playVideo(url, location = "center", size = 5, duration = null) { console.log('[UI] playVideo called:', { url, location, size, duration }); backendDuration = typeof duration === 'number' && duration > 0 ? duration : null; videoPlayer.src = url; videoOverlay.classList.remove('hidden'); setOverlayPosition(location, size); // Show loading spinner, hide video and progress until ready loadingSpinner.style.display = 'flex'; videoPlayer.style.display = 'none'; progressContainer.style.display = backendDuration ? 'block' : 'none'; // Start loading and wait for canplay to avoid black frames videoPlayer.load(); console.log('[UI] Video loading started.'); const cleanup = () => { videoPlayer.removeEventListener('canplay', onCanPlay); videoPlayer.removeEventListener('error', onError); }; const onCanPlay = () => { console.log('[UI] canplay: starting playback'); loadingSpinner.style.display = 'none'; videoPlayer.style.display = ''; videoPlayer.play().then(() => { console.log('[UI] Video playback started.'); }).catch((err) => { console.warn('[UI] Video playback failed:', err); }); updateProgress(); cleanup(); }; const onError = (e) => { console.error('[UI] Video error:', e); showError(e.message ? e.message.toString() : 'Unknown video error'); cleanup(); }; videoPlayer.addEventListener('canplay', onCanPlay, { once: true }); videoPlayer.addEventListener('error', onError, { once: true }); // Safety fallback in case canplay doesn't fire quickly setTimeout(() => { if (loadingSpinner.style.display !== 'none') { console.warn('[UI] Fallback: forcing playback after timeout.'); onCanPlay(); } }, 2000); } function stopVideo() { console.log('stopVideo called.'); hideVideo(); } function sendStoppedMessage() { if (ws && ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ type: "stopped" })); console.log('Sent "stopped" message to backend.'); } } function hideVideo() { console.log('hideVideo called.'); backendDuration = null; // Clear backend-provided duration videoOverlay.classList.add('hidden'); videoPlayer.pause(); videoPlayer.src = ''; videoPlayer.style.width = '100vw'; videoPlayer.style.height = '100vh'; progressContainer.style.display = 'none'; progressFill.style.width = '0%'; timer.textContent = ''; loadingSpinner.style.display = 'none'; videoPlayer.style.display = ''; sendStoppedMessage(); // Notify backend when video stops } // Start WebSocket connection when page loads console.log('Starting WebSocket connection...'); connectWebSocket(); videoPlayer.addEventListener('ended', () => { console.log('Video ended event.'); hideVideo(); sendStoppedMessage(); // Notify backend when video ends }); videoPlayer.addEventListener('timeupdate', () => { // This can be noisy, so only log occasionally if (Math.floor(videoPlayer.currentTime) % 5 === 0) { console.log('Video timeupdate:', videoPlayer.currentTime); } updateProgress(); }); function updateProgress() { let current = 0; try { current = Number(videoPlayer.currentTime) || 0; if (typeof backendDuration === 'number' && backendDuration > 0) { const total = backendDuration; const percent = Math.min((current / total) * 100, 100); progressFill.style.width = percent + '%'; timer.textContent = formatTime(current) + ' / ' + formatTime(total); // Optional: reduce noisy logs // console.log(`[UI] Progress: ${percent.toFixed(2)}%`); } else { // No duration provided; hide progress UI progressFill.style.width = '0%'; timer.textContent = formatTime(current) + ' / --:--'; progressContainer.style.display = 'none'; } } catch (err) { progressFill.style.width = '0%'; timer.textContent = formatTime(current) + ' / --:--'; console.error('[UI] Error updating progress:', err); } } function formatTime(seconds) { seconds = Math.floor(seconds); const m = Math.floor(seconds / 60); const s = seconds % 60; return `${m}:${s.toString().padStart(2, '0')}`; } function setOverlayPosition(location, size) { console.log('setOverlayPosition called:', { location, size }); // Reset styles videoOverlay.style.justifyContent = ''; videoOverlay.style.alignItems = ''; videoOverlay.style.top = ''; videoOverlay.style.left = ''; videoOverlay.style.right = ''; videoOverlay.style.bottom = ''; videoOverlay.style.width = '100vw'; videoOverlay.style.height = '100vh'; // Calculate video size as percentage of viewport (10% to 100%) const percent = Math.min(Math.max(size, 1), 10) * 10; videoPlayer.style.width = percent + 'vw'; videoPlayer.style.height = percent + 'vh'; // Position overlay and video switch (location) { case 'top_left': videoOverlay.style.justifyContent = 'flex-start'; videoOverlay.style.alignItems = 'flex-start'; break; case 'bottom_left': videoOverlay.style.justifyContent = 'flex-start'; videoOverlay.style.alignItems = 'flex-end'; break; case 'top_right': videoOverlay.style.justifyContent = 'flex-end'; videoOverlay.style.alignItems = 'flex-start'; break; case 'center': default: videoOverlay.style.justifyContent = 'center'; videoOverlay.style.alignItems = 'center'; break; } console.log('Overlay position set.'); }