/** * Quad Video Player - Multi-screen video overlay system * Displays 4 videos simultaneously in a grid layout for intensive viewing sessions */ class QuadVideoPlayer { constructor() { this.players = []; this.container = null; this.isActive = false; this.isMinimized = false; this.videoLibrary = null; } /** * Initialize the quad video player system */ async initialize() { // Get video library using the same pattern as OverlayVideoPlayer this.videoLibrary = this.getAvailableVideos(); if (!this.videoLibrary || this.videoLibrary.length === 0) { console.warn('⚠️ No videos available for quad player'); return false; } this.createQuadContainer(); await this.initializePlayers(); return true; } /** * Get available videos using the same logic as OverlayVideoPlayer */ getAvailableVideos() { let allVideos = []; // Try desktop file manager first if (window.desktopFileManager) { allVideos = window.desktopFileManager.getAllVideos(); } // Fallback to unified storage if (allVideos.length === 0) { const unifiedData = JSON.parse(localStorage.getItem('unifiedVideoLibrary') || '{}'); allVideos = unifiedData.allVideos || []; } // Fallback to legacy storage if (allVideos.length === 0) { const storedVideos = JSON.parse(localStorage.getItem('videoFiles') || '{}'); allVideos = Object.values(storedVideos).flat(); } return allVideos; } /** * Create the main quad container */ createQuadContainer() { this.container = document.createElement('div'); this.container.className = 'quad-video-overlay'; this.container.innerHTML = `

🎬 Multi-Screen Training Mode

`; // Add to body but keep hidden initially this.container.style.display = 'none'; document.body.appendChild(this.container); // Add event listeners for controls this.setupEventListeners(); this.addQuadStyles(); } /** * Initialize 4 overlay video players */ async initializePlayers() { for (let i = 0; i < 4; i++) { const player = new OverlayVideoPlayer(); // Configure for quad layout this.setupQuadPlayer(player, i); this.players.push(player); } } /** * Configure individual player for quad layout */ setupQuadPlayer(player, index) { const slot = this.container.querySelector(`[data-slot="${index}"]`); // Ensure the overlay element is in the DOM (constructor removes it) if (!document.body.contains(player.overlayElement)) { document.body.appendChild(player.overlayElement); } // Configure the overlay element for quad layout player.overlayElement.style.position = 'relative'; player.overlayElement.style.width = '100%'; player.overlayElement.style.height = '100%'; player.overlayElement.style.background = 'transparent'; player.overlayElement.style.display = 'flex'; // Override the default display: none // Set visibility states player.isVisible = true; player.overlayElement.classList.add('visible'); // Remove/hide the backdrop const backdrop = player.overlayElement.querySelector('.overlay-backdrop'); if (backdrop) { backdrop.style.display = 'none'; } // Find and configure the overlay window const overlayWindow = player.overlayElement.querySelector('.overlay-window'); if (overlayWindow) { overlayWindow.style.position = 'relative'; overlayWindow.style.width = '100%'; overlayWindow.style.height = '100%'; overlayWindow.style.margin = '0'; overlayWindow.style.transform = 'none'; overlayWindow.style.maxWidth = 'none'; overlayWindow.style.maxHeight = 'none'; } // Find and configure the video container const videoContainer = player.overlayElement.querySelector('.overlay-video-container'); if (videoContainer) { videoContainer.style.width = '100%'; videoContainer.style.height = '100%'; videoContainer.style.position = 'relative'; } // Find and configure the video element const video = player.overlayElement.querySelector('video'); if (video) { video.style.width = '100%'; video.style.height = '100%'; video.style.objectFit = 'contain'; // Changed from 'cover' to 'contain' to fit entire video video.style.display = 'block'; video.style.backgroundColor = '#000'; // Add black background for letterboxing } // Hide the original header to save space const header = player.overlayElement.querySelector('.overlay-header'); if (header) { header.style.display = 'none'; } // Hide individual close button const closeBtn = player.overlayElement.querySelector('.overlay-close-btn'); if (closeBtn) { closeBtn.style.display = 'none'; } // Hide the default video controls since we're adding our own const defaultControls = player.overlayElement.querySelector('.video-controls'); if (defaultControls) { defaultControls.style.display = 'none'; } // Hide the large play button overlay since we have our own controls const playOverlay = player.overlayElement.querySelector('.play-overlay'); if (playOverlay) { playOverlay.style.display = 'none'; } // Create individual controls for this quadrant this.createIndividualControls(player, index); // Remove from body and add to quad slot document.body.removeChild(player.overlayElement); slot.appendChild(player.overlayElement); // Load random video for this slot player.playRandomVideo(); } /** * Create individual controls for each quadrant */ createIndividualControls(player, index) { // Create controls container const controlsContainer = document.createElement('div'); controlsContainer.className = 'quad-individual-controls'; controlsContainer.dataset.quadIndex = index; controlsContainer.innerHTML = `
70%
0:00 / 0:00
`; // Insert controls into the overlay window const overlayWindow = player.overlayElement.querySelector('.overlay-window'); if (overlayWindow) { overlayWindow.appendChild(controlsContainer); } // Setup event listeners for individual controls this.setupIndividualControlListeners(player, index, controlsContainer); } /** * Setup event listeners for individual quadrant controls */ setupIndividualControlListeners(player, index, controlsContainer) { const video = player.overlayElement.querySelector('video'); if (!video) return; // Play/Pause button const playPauseBtn = controlsContainer.querySelector('.play-pause-btn'); const playIcon = playPauseBtn.querySelector('.play-icon'); const pauseIcon = playPauseBtn.querySelector('.pause-icon'); playPauseBtn.addEventListener('click', () => { if (video.paused) { video.play(); playIcon.style.display = 'none'; pauseIcon.style.display = 'inline'; } else { video.pause(); playIcon.style.display = 'inline'; pauseIcon.style.display = 'none'; } }); // Update play/pause button based on video state video.addEventListener('play', () => { playIcon.style.display = 'none'; pauseIcon.style.display = 'inline'; }); video.addEventListener('pause', () => { playIcon.style.display = 'inline'; pauseIcon.style.display = 'none'; }); // Volume control const volumeSlider = controlsContainer.querySelector('.quad-volume-slider'); const volumeDisplay = controlsContainer.querySelector('.quad-volume-display'); volumeSlider.addEventListener('input', () => { const volume = volumeSlider.value / 100; video.volume = volume; volumeDisplay.textContent = `${volumeSlider.value}%`; // Update mute button state const muteBtn = controlsContainer.querySelector('.mute-btn'); const unmutedIcon = muteBtn.querySelector('.unmuted-icon'); const mutedIcon = muteBtn.querySelector('.muted-icon'); if (volume === 0) { unmutedIcon.style.display = 'none'; mutedIcon.style.display = 'inline'; } else { unmutedIcon.style.display = 'inline'; mutedIcon.style.display = 'none'; } }); // Mute button const muteBtn = controlsContainer.querySelector('.mute-btn'); const unmutedIcon = muteBtn.querySelector('.unmuted-icon'); const mutedIcon = muteBtn.querySelector('.muted-icon'); let previousVolume = 0.7; muteBtn.addEventListener('click', () => { if (video.volume > 0) { previousVolume = video.volume; video.volume = 0; volumeSlider.value = 0; volumeDisplay.textContent = '0%'; unmutedIcon.style.display = 'none'; mutedIcon.style.display = 'inline'; } else { video.volume = previousVolume; volumeSlider.value = previousVolume * 100; volumeDisplay.textContent = `${Math.round(previousVolume * 100)}%`; unmutedIcon.style.display = 'inline'; mutedIcon.style.display = 'none'; } }); // Shuffle/Change video button const shuffleBtn = controlsContainer.querySelector('.shuffle-btn'); shuffleBtn.addEventListener('click', () => { player.playRandomVideo(); }); // Progress bar functionality const progressContainer = controlsContainer.querySelector('.quad-progress-container'); const progressBar = controlsContainer.querySelector('.quad-progress-bar'); const progressFilled = controlsContainer.querySelector('.quad-progress-filled'); const progressThumb = controlsContainer.querySelector('.quad-progress-thumb'); const currentTimeDisplay = controlsContainer.querySelector('.quad-current-time'); const totalTimeDisplay = controlsContainer.querySelector('.quad-total-time'); // Update progress bar video.addEventListener('timeupdate', () => { if (video.duration && video.duration > 0) { const percentage = (video.currentTime / video.duration) * 100; progressFilled.style.width = `${percentage}%`; progressThumb.style.left = `${percentage}%`; currentTimeDisplay.textContent = this.formatTime(video.currentTime); totalTimeDisplay.textContent = this.formatTime(video.duration); } }); // Progress bar seeking progressBar.addEventListener('click', (e) => { if (video.duration) { const rect = progressBar.getBoundingClientRect(); const clickX = e.clientX - rect.left; const percentage = clickX / rect.width; video.currentTime = percentage * video.duration; } }); // Initialize time displays video.addEventListener('loadedmetadata', () => { totalTimeDisplay.textContent = this.formatTime(video.duration); currentTimeDisplay.textContent = this.formatTime(0); }); } /** * Setup event listeners for quad controls */ setupEventListeners() { // Shuffle all videos const shuffleBtn = this.container.querySelector('#quad-shuffle-btn'); shuffleBtn?.addEventListener('click', () => { this.shuffleAllVideos(); }); // Minimize quad mode const minimizeBtn = this.container.querySelector('#quad-minimize-btn'); minimizeBtn?.addEventListener('click', () => { this.minimize(); }); // Close quad mode const closeBtn = this.container.querySelector('#quad-close-btn'); closeBtn?.addEventListener('click', () => { this.hide(); }); // ESC key to close document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && this.isActive) { this.hide(); } }); // Backdrop click to close this.container.addEventListener('click', (e) => { if (e.target === this.container) { this.hide(); } }); } /** * Show the quad video overlay */ show() { if (!this.container) { console.error('❌ QuadVideoPlayer not initialized'); return; } this.container.style.display = 'flex'; this.isActive = true; // Start all videos this.players.forEach(player => { if (player.videoElement) { player.videoElement.play().catch(e => console.log('Video autoplay prevented:', e)); } }); } /** * Hide the quad video overlay */ hide() { if (!this.container) return; this.container.style.display = 'none'; this.isActive = false; this.isMinimized = false; // Reset minimized state when fully hiding // Always pause and stop all videos, regardless of current state this.stopAllVideos(); // Reset player states more thoroughly this.players.forEach((player, index) => { if (player.overlayElement) { // Reset video source to prevent corruption if (player.videoElement) { player.videoElement.src = ''; player.videoElement.load(); // Force reload to clear state } // Reset player state player.isVisible = false; player.currentVideo = null; player.overlayElement.classList.remove('visible'); } }); } /** * Stop all videos (separate method for cleanup purposes) */ stopAllVideos() { this.players.forEach((player, index) => { // Use videoElement (BaseVideoPlayer property) instead of video if (player.videoElement) { player.videoElement.pause(); player.videoElement.currentTime = 0; // Reset to beginning } // Also hide the individual players if (player.overlayElement) { player.isVisible = false; player.overlayElement.classList.remove('visible'); } }); } /** * Minimize the quad video overlay (hide but keep videos playing) */ minimize() { if (!this.container) return; this.container.style.display = 'none'; this.isMinimized = true; // Note: Keep isActive = true and videos continue playing // Show notification to user if (window.game && window.game.showNotification) { window.game.showNotification('πŸ“Ί Multi-Screen Mode minimized - videos continue in background. Click Multi-Screen button to restore.', 'info'); } } /** * Restore from minimized state */ restore() { if (!this.container || !this.isMinimized) return; this.container.style.display = 'flex'; this.isMinimized = false; this.isActive = true; } /** * Load new random videos in all slots */ shuffleAllVideos() { // Refresh video library in case new videos were added this.videoLibrary = this.getAvailableVideos(); if (this.videoLibrary.length === 0) { console.warn('⚠️ No videos available for shuffle'); return; } this.players.forEach(player => { player.playRandomVideo(); }); } /** * Add CSS styles for quad layout */ addQuadStyles() { if (document.getElementById('quad-video-styles')) return; const styles = document.createElement('style'); styles.id = 'quad-video-styles'; styles.textContent = ` .quad-video-overlay { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: rgba(0, 0, 0, 0.95); z-index: 10000; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 20px; box-sizing: border-box; } .quad-header { display: flex; justify-content: space-between; align-items: center; width: 100%; max-width: 1200px; margin-bottom: 20px; color: white; } .quad-header h2 { margin: 0; color: #ff6b9d; text-shadow: 0 2px 4px rgba(0,0,0,0.5); } .quad-controls { display: flex; gap: 10px; } .quad-controls .btn { transition: all 0.3s ease; } .quad-controls .btn:hover { transform: translateY(-1px); } .quad-grid { display: grid; grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr; gap: 15px; width: 100%; height: calc(100vh - 120px); max-width: 1200px; max-height: 800px; } .quad-slot { position: relative; background: #000; border-radius: 12px; overflow: hidden; border: 2px solid #333; box-shadow: 0 4px 15px rgba(0,0,0,0.3); } .quad-slot:hover { border-color: #ff6b9d; transform: scale(1.02); transition: all 0.3s ease; } /* Force overlay elements to display properly in quad layout */ .quad-slot .video-overlay-popup { position: relative !important; width: 100% !important; height: 100% !important; background: transparent !important; display: block !important; } .quad-slot .overlay-window { position: relative !important; width: 100% !important; height: 100% !important; margin: 0 !important; transform: none !important; max-width: none !important; max-height: none !important; background: transparent !important; } .quad-slot .overlay-video-container { width: 100% !important; height: 100% !important; position: relative !important; } .quad-slot video { width: 100% !important; height: 100% !important; object-fit: contain !important; /* Ensure entire video is visible */ display: block !important; background-color: #000 !important; /* Black background for letterboxing */ } /* Responsive design for smaller screens */ @media (max-width: 768px) { .quad-grid { grid-template-columns: 1fr; grid-template-rows: repeat(4, 1fr); gap: 10px; max-height: 90vh; } .quad-header { flex-direction: column; gap: 10px; text-align: center; } } @media (max-width: 480px) { .quad-video-overlay { padding: 10px; } .quad-grid { gap: 8px; } } /* Individual Controls Styling */ .quad-individual-controls { position: absolute; bottom: 0; left: 0; right: 0; background: linear-gradient(to top, rgba(0,0,0,0.9) 0%, rgba(0,0,0,0.7) 50%, transparent 100%); padding: 8px 12px; opacity: 0; transition: opacity 0.3s ease; z-index: 10; } .quad-slot:hover .quad-individual-controls { opacity: 1; } .quad-control-row { display: flex; align-items: center; gap: 8px; margin-bottom: 4px; } .quad-progress-row { display: flex; align-items: center; gap: 8px; } .quad-control-btn { background: rgba(255, 255, 255, 0.2); border: none; padding: 4px 8px; border-radius: 4px; color: white; font-size: 12px; cursor: pointer; transition: all 0.2s ease; min-width: 28px; height: 24px; display: flex; align-items: center; justify-content: center; } .quad-control-btn:hover { background: rgba(255, 255, 255, 0.3); transform: scale(1.05); } .quad-volume-control { display: flex; align-items: center; gap: 4px; flex: 1; } .quad-volume-slider { flex: 1; height: 4px; background: rgba(255, 255, 255, 0.3); border-radius: 2px; outline: none; cursor: pointer; -webkit-appearance: none; appearance: none; } .quad-volume-slider::-webkit-slider-thumb { appearance: none; width: 12px; height: 12px; border-radius: 50%; background: #ff6b9d; cursor: pointer; border: 2px solid white; box-shadow: 0 1px 3px rgba(0,0,0,0.3); } .quad-volume-slider::-moz-range-thumb { width: 12px; height: 12px; border-radius: 50%; background: #ff6b9d; cursor: pointer; border: 2px solid white; box-shadow: 0 1px 3px rgba(0,0,0,0.3); } .quad-volume-display { font-size: 10px; color: white; min-width: 30px; text-align: right; } .quad-progress-container { flex: 1; display: flex; flex-direction: column; gap: 2px; } .quad-progress-bar { position: relative; height: 4px; background: rgba(255, 255, 255, 0.3); border-radius: 2px; cursor: pointer; overflow: visible; } .quad-progress-filled { height: 100%; background: #ff6b9d; border-radius: 2px; width: 0%; transition: width 0.1s ease; } .quad-progress-thumb { position: absolute; top: 50%; transform: translate(-50%, -50%); width: 8px; height: 8px; background: white; border-radius: 50%; box-shadow: 0 1px 3px rgba(0,0,0,0.5); left: 0%; transition: left 0.1s ease; } .quad-time-display { font-size: 9px; color: rgba(255, 255, 255, 0.8); text-align: center; white-space: nowrap; } /* Hide controls on mobile to save space */ @media (max-width: 768px) { .quad-individual-controls { padding: 4px 6px; } .quad-control-btn { padding: 2px 4px; font-size: 10px; min-width: 20px; height: 20px; } .quad-volume-display { font-size: 8px; min-width: 25px; } .quad-time-display { font-size: 8px; } } `; document.head.appendChild(styles); } /** * Format time in MM:SS format */ formatTime(seconds) { if (!seconds || isNaN(seconds)) return '0:00'; const minutes = Math.floor(seconds / 60); const remainingSeconds = Math.floor(seconds % 60); return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`; } /** * Cleanup resources */ destroy() { // Stop all videos first this.stopAllVideos(); // Destroy individual players this.players.forEach((player, index) => { // Remove event listeners and clean up player if (player.overlayElement) { // Remove from DOM if (player.overlayElement.parentNode) { player.overlayElement.parentNode.removeChild(player.overlayElement); } } // Call destroy method if it exists if (typeof player.destroy === 'function') { player.destroy(); } }); // Remove container if (this.container && this.container.parentNode) { this.container.parentNode.removeChild(this.container); } // Reset state this.players = []; this.container = null; this.isActive = false; this.isMinimized = false; // Remove styles const styles = document.getElementById('quad-video-styles'); if (styles) { styles.remove(); } } } // Export for use in other modules if (typeof module !== 'undefined' && module.exports) { module.exports = QuadVideoPlayer; } // Make available globally window.QuadVideoPlayer = QuadVideoPlayer;