536 lines
16 KiB
JavaScript
536 lines
16 KiB
JavaScript
/**
|
|
* Video Player Manager
|
|
* Handles video playback, management, and integration with game modes
|
|
*/
|
|
|
|
class VideoPlayerManager {
|
|
constructor() {
|
|
this.currentPlayer = null;
|
|
this.backgroundPlayer = null;
|
|
this.overlayPlayer = null;
|
|
this.videoLibrary = {
|
|
background: [],
|
|
task: [],
|
|
reward: [],
|
|
punishment: []
|
|
};
|
|
this.settings = {
|
|
enabled: true,
|
|
backgroundVideos: true,
|
|
taskVideos: true,
|
|
autoplay: true,
|
|
loop: true,
|
|
volume: 0.3,
|
|
muted: false,
|
|
playbackRate: 1.0,
|
|
showControls: false,
|
|
fadeTransitions: true
|
|
};
|
|
this.isInitialized = false;
|
|
this.loadSettings();
|
|
}
|
|
|
|
/**
|
|
* Initialize the video player system
|
|
*/
|
|
async init() {
|
|
console.log('🎬 Initializing Video Player Manager...');
|
|
|
|
try {
|
|
this.createVideoPlayers();
|
|
this.bindEvents();
|
|
await this.scanVideoLibrary();
|
|
this.isInitialized = true;
|
|
console.log('✅ Video Player Manager initialized successfully');
|
|
} catch (error) {
|
|
console.error('❌ Failed to initialize Video Player Manager:', error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create video player elements
|
|
*/
|
|
createVideoPlayers() {
|
|
// Background video player (behind content)
|
|
this.backgroundPlayer = this.createVideoElement('background-video-player', {
|
|
loop: true,
|
|
muted: true,
|
|
autoplay: true,
|
|
style: `
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100vw;
|
|
height: 100vh;
|
|
object-fit: cover;
|
|
z-index: -1;
|
|
opacity: 0.3;
|
|
pointer-events: none;
|
|
`
|
|
});
|
|
|
|
// Task video player (in content area)
|
|
this.currentPlayer = this.createVideoElement('task-video-player', {
|
|
controls: this.settings.showControls,
|
|
style: `
|
|
max-width: 100%;
|
|
max-height: 70vh;
|
|
border-radius: 8px;
|
|
display: none;
|
|
`
|
|
});
|
|
|
|
// Overlay video player (for popups/punishments)
|
|
this.overlayPlayer = this.createVideoElement('overlay-video-player', {
|
|
autoplay: true,
|
|
style: `
|
|
position: fixed;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
max-width: 80vw;
|
|
max-height: 80vh;
|
|
z-index: 10000;
|
|
border-radius: 8px;
|
|
box-shadow: 0 0 50px rgba(0,0,0,0.8);
|
|
display: none;
|
|
`
|
|
});
|
|
|
|
// Add players to DOM
|
|
document.body.appendChild(this.backgroundPlayer);
|
|
document.body.appendChild(this.overlayPlayer);
|
|
|
|
// Add task player to game container
|
|
const gameContainer = document.querySelector('.game-container') || document.body;
|
|
gameContainer.appendChild(this.currentPlayer);
|
|
}
|
|
|
|
/**
|
|
* Create a video element with specified attributes
|
|
*/
|
|
createVideoElement(id, attributes = {}) {
|
|
const video = document.createElement('video');
|
|
video.id = id;
|
|
|
|
// Set default attributes
|
|
video.preload = 'metadata';
|
|
video.playsInline = true;
|
|
video.webkit = { playsinline: true };
|
|
|
|
// Apply custom attributes
|
|
Object.entries(attributes).forEach(([key, value]) => {
|
|
if (key === 'style') {
|
|
video.style.cssText = value;
|
|
} else {
|
|
video[key] = value;
|
|
}
|
|
});
|
|
|
|
// Add event listeners
|
|
video.addEventListener('loadstart', () => console.log(`🎬 Loading video: ${video.src}`));
|
|
video.addEventListener('canplay', () => console.log(`✅ Video ready: ${video.src}`));
|
|
video.addEventListener('error', (e) => console.error(`❌ Video error: ${e.message}`));
|
|
video.addEventListener('ended', (e) => this.handleVideoEnd(e));
|
|
|
|
return video;
|
|
}
|
|
|
|
/**
|
|
* Scan video directory for available videos
|
|
*/
|
|
async scanVideoLibrary() {
|
|
console.log('📁 Scanning video library...');
|
|
|
|
const categories = ['background', 'task', 'reward', 'punishment'];
|
|
|
|
for (const category of categories) {
|
|
try {
|
|
// In Electron environment, use the desktop file manager
|
|
if (window.electronAPI && window.desktopFileManager) {
|
|
// Use unified video library - get all videos and filter by category if needed
|
|
const allVideos = window.desktopFileManager.getAllVideos();
|
|
|
|
// For now, just use all videos for any category since we removed categories
|
|
// In the future, we could add tags or other filtering mechanisms
|
|
this.videoLibrary[category] = allVideos;
|
|
} else {
|
|
// In browser environment, try to load from unified storage
|
|
const unifiedData = JSON.parse(localStorage.getItem('unifiedVideoLibrary') || '{}');
|
|
this.videoLibrary[category] = unifiedData.allVideos || [];
|
|
}
|
|
|
|
console.log(`📹 Found ${this.videoLibrary[category].length} ${category} videos (from unified library)`);
|
|
} catch (error) {
|
|
console.warn(`⚠️ Could not load ${category} videos:`, error);
|
|
this.videoLibrary[category] = [];
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get known videos for browser environment (fallback)
|
|
*/
|
|
getKnownVideos(category) {
|
|
// This would need to be populated manually in browser environment
|
|
// or through a manifest file
|
|
return [];
|
|
}
|
|
|
|
/**
|
|
* Reload video files from storage (called when new videos are imported)
|
|
*/
|
|
async loadVideoFiles() {
|
|
console.log('🔄 Reloading video library...');
|
|
await this.scanVideoLibrary();
|
|
}
|
|
|
|
/**
|
|
* Play background video
|
|
*/
|
|
async playBackgroundVideo(videoPath = null) {
|
|
if (!this.settings.backgroundVideos || !this.backgroundPlayer) return;
|
|
|
|
try {
|
|
if (!videoPath) {
|
|
const backgroundVideos = this.videoLibrary.background;
|
|
if (backgroundVideos.length === 0) return;
|
|
videoPath = backgroundVideos[Math.floor(Math.random() * backgroundVideos.length)];
|
|
}
|
|
|
|
const fullPath = this.getVideoPath(videoPath);
|
|
console.log(`🎬 Playing background video: ${fullPath}`);
|
|
|
|
if (this.settings.fadeTransitions) {
|
|
await this.fadeOut(this.backgroundPlayer);
|
|
}
|
|
|
|
this.backgroundPlayer.src = fullPath;
|
|
this.backgroundPlayer.volume = this.settings.volume * 0.5; // Background videos quieter
|
|
this.backgroundPlayer.muted = this.settings.muted;
|
|
this.backgroundPlayer.playbackRate = this.settings.playbackRate;
|
|
|
|
await this.backgroundPlayer.play();
|
|
|
|
if (this.settings.fadeTransitions) {
|
|
await this.fadeIn(this.backgroundPlayer);
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('❌ Failed to play background video:', error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Play task video
|
|
*/
|
|
async playTaskVideo(videoPath, container = null) {
|
|
if (!this.settings.taskVideos || !this.currentPlayer) return;
|
|
|
|
try {
|
|
const fullPath = this.getVideoPath(videoPath);
|
|
console.log(`🎬 Playing task video: ${fullPath}`);
|
|
|
|
// Position player in specified container
|
|
if (container) {
|
|
container.appendChild(this.currentPlayer);
|
|
}
|
|
|
|
this.currentPlayer.src = fullPath;
|
|
this.currentPlayer.volume = this.settings.volume;
|
|
this.currentPlayer.muted = this.settings.muted;
|
|
this.currentPlayer.controls = this.settings.showControls;
|
|
this.currentPlayer.style.display = 'block';
|
|
|
|
if (this.settings.autoplay) {
|
|
await this.currentPlayer.play();
|
|
}
|
|
|
|
if (this.settings.fadeTransitions) {
|
|
await this.fadeIn(this.currentPlayer);
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('❌ Failed to play task video:', error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Play overlay video (for punishments/rewards)
|
|
*/
|
|
async playOverlayVideo(videoPath, options = {}) {
|
|
if (!this.overlayPlayer) return;
|
|
|
|
try {
|
|
const fullPath = this.getVideoPath(videoPath);
|
|
console.log(`🎬 Playing overlay video: ${fullPath}`);
|
|
|
|
const {
|
|
duration = null,
|
|
closeable = false,
|
|
onComplete = null,
|
|
volume = this.settings.volume
|
|
} = options;
|
|
|
|
this.overlayPlayer.src = fullPath;
|
|
this.overlayPlayer.volume = volume;
|
|
this.overlayPlayer.muted = this.settings.muted;
|
|
this.overlayPlayer.style.display = 'block';
|
|
|
|
// Add close button if closeable
|
|
if (closeable) {
|
|
this.addCloseButton(this.overlayPlayer);
|
|
}
|
|
|
|
await this.overlayPlayer.play();
|
|
|
|
if (this.settings.fadeTransitions) {
|
|
await this.fadeIn(this.overlayPlayer);
|
|
}
|
|
|
|
// Auto-close after duration
|
|
if (duration) {
|
|
setTimeout(() => {
|
|
this.closeOverlayVideo();
|
|
if (onComplete) onComplete();
|
|
}, duration);
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('❌ Failed to play overlay video:', error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Close overlay video
|
|
*/
|
|
async closeOverlayVideo() {
|
|
if (!this.overlayPlayer) return;
|
|
|
|
if (this.settings.fadeTransitions) {
|
|
await this.fadeOut(this.overlayPlayer);
|
|
}
|
|
|
|
this.overlayPlayer.pause();
|
|
this.overlayPlayer.style.display = 'none';
|
|
this.overlayPlayer.src = '';
|
|
|
|
// Remove close button if exists
|
|
const closeBtn = this.overlayPlayer.nextElementSibling;
|
|
if (closeBtn && closeBtn.classList.contains('video-close-btn')) {
|
|
closeBtn.remove();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Stop all videos
|
|
*/
|
|
stopAllVideos() {
|
|
[this.backgroundPlayer, this.currentPlayer, this.overlayPlayer].forEach(player => {
|
|
if (player) {
|
|
player.pause();
|
|
player.currentTime = 0;
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Pause all videos
|
|
*/
|
|
pauseAllVideos() {
|
|
[this.backgroundPlayer, this.currentPlayer, this.overlayPlayer].forEach(player => {
|
|
if (player && !player.paused) {
|
|
player.pause();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Resume all videos
|
|
*/
|
|
resumeAllVideos() {
|
|
[this.backgroundPlayer, this.currentPlayer, this.overlayPlayer].forEach(player => {
|
|
if (player && player.paused) {
|
|
player.play().catch(console.error);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get random video from category
|
|
*/
|
|
getRandomVideo(category) {
|
|
const videos = this.videoLibrary[category] || [];
|
|
if (videos.length === 0) return null;
|
|
return videos[Math.floor(Math.random() * videos.length)];
|
|
}
|
|
|
|
/**
|
|
* Get full path for video
|
|
*/
|
|
getVideoPath(videoPath) {
|
|
// Already a full URL
|
|
if (videoPath.startsWith('http') || videoPath.startsWith('file:')) {
|
|
return videoPath;
|
|
}
|
|
|
|
// Absolute path (Windows: C:\... or E:\..., Unix: /...)
|
|
if (videoPath.match(/^[A-Z]:\\/i) || videoPath.startsWith('/')) {
|
|
// Convert to file:// URL for Electron
|
|
const normalizedPath = videoPath.replace(/\\/g, '/');
|
|
return `file:///${normalizedPath}`;
|
|
}
|
|
|
|
// Relative path - add video/ prefix
|
|
return `video/${videoPath}`;
|
|
}
|
|
|
|
/**
|
|
* Add close button to video
|
|
*/
|
|
addCloseButton(videoElement) {
|
|
const closeBtn = document.createElement('button');
|
|
closeBtn.className = 'video-close-btn';
|
|
closeBtn.innerHTML = '✕';
|
|
closeBtn.style.cssText = `
|
|
position: fixed;
|
|
top: calc(${videoElement.style.top} - 30px);
|
|
right: calc(50% - ${videoElement.offsetWidth/2}px + ${videoElement.offsetWidth}px - 30px);
|
|
z-index: 10001;
|
|
background: rgba(0,0,0,0.8);
|
|
color: white;
|
|
border: none;
|
|
width: 30px;
|
|
height: 30px;
|
|
border-radius: 50%;
|
|
cursor: pointer;
|
|
font-size: 16px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
`;
|
|
|
|
closeBtn.addEventListener('click', () => this.closeOverlayVideo());
|
|
videoElement.parentNode.insertBefore(closeBtn, videoElement.nextSibling);
|
|
}
|
|
|
|
/**
|
|
* Fade in animation
|
|
*/
|
|
fadeIn(element, duration = 500) {
|
|
return new Promise(resolve => {
|
|
element.style.opacity = '0';
|
|
element.style.transition = `opacity ${duration}ms ease-in-out`;
|
|
|
|
requestAnimationFrame(() => {
|
|
element.style.opacity = element.id === 'background-video-player' ? '0.3' : '1';
|
|
setTimeout(resolve, duration);
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Fade out animation
|
|
*/
|
|
fadeOut(element, duration = 500) {
|
|
return new Promise(resolve => {
|
|
element.style.transition = `opacity ${duration}ms ease-in-out`;
|
|
element.style.opacity = '0';
|
|
setTimeout(resolve, duration);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Handle video end events
|
|
*/
|
|
handleVideoEnd(event) {
|
|
const video = event.target;
|
|
|
|
if (video === this.backgroundPlayer && this.settings.loop) {
|
|
// Start next background video
|
|
this.playBackgroundVideo();
|
|
} else if (video === this.overlayPlayer) {
|
|
// Auto-close overlay videos
|
|
this.closeOverlayVideo();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update settings
|
|
*/
|
|
updateSettings(newSettings) {
|
|
this.settings = { ...this.settings, ...newSettings };
|
|
this.saveSettings();
|
|
this.applySettings();
|
|
}
|
|
|
|
/**
|
|
* Apply current settings to players
|
|
*/
|
|
applySettings() {
|
|
[this.backgroundPlayer, this.currentPlayer, this.overlayPlayer].forEach(player => {
|
|
if (player) {
|
|
player.volume = this.settings.volume;
|
|
player.muted = this.settings.muted;
|
|
player.playbackRate = this.settings.playbackRate;
|
|
if (player === this.currentPlayer) {
|
|
player.controls = this.settings.showControls;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Load settings from localStorage
|
|
*/
|
|
loadSettings() {
|
|
const saved = localStorage.getItem('videoPlayerSettings');
|
|
if (saved) {
|
|
this.settings = { ...this.settings, ...JSON.parse(saved) };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Save settings to localStorage
|
|
*/
|
|
saveSettings() {
|
|
localStorage.setItem('videoPlayerSettings', JSON.stringify(this.settings));
|
|
}
|
|
|
|
/**
|
|
* Bind event listeners
|
|
*/
|
|
bindEvents() {
|
|
// Game pause/resume integration
|
|
document.addEventListener('gamepaused', () => this.pauseAllVideos());
|
|
document.addEventListener('gameresumed', () => this.resumeAllVideos());
|
|
|
|
// Cleanup on page unload
|
|
window.addEventListener('beforeunload', () => this.stopAllVideos());
|
|
}
|
|
|
|
/**
|
|
* Get video library info
|
|
*/
|
|
getLibraryInfo() {
|
|
return {
|
|
background: this.videoLibrary.background.length,
|
|
task: this.videoLibrary.task.length,
|
|
reward: this.videoLibrary.reward.length,
|
|
punishment: this.videoLibrary.punishment.length,
|
|
total: Object.values(this.videoLibrary).flat().length
|
|
};
|
|
}
|
|
}
|
|
|
|
// Create global instance
|
|
window.videoPlayerManager = new VideoPlayerManager();
|
|
|
|
// Auto-initialize when DOM is ready
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
if (window.videoPlayerManager) {
|
|
window.videoPlayerManager.init();
|
|
}
|
|
});
|
|
|
|
console.log('🎬 Video Player Manager loaded'); |