training-academy/src/features/video/videoPlayerManager.js

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');