/**
* 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 = `
`;
// 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 = `
`;
// 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;