class PornCinema extends BaseVideoPlayer {
constructor() {
super('#video-container', {
showControls: true,
autoHide: true,
showProgress: true,
showVolume: true,
showFullscreen: true,
showQuality: true,
showSpeed: true,
keyboardShortcuts: true,
minimal: false
});
// Cinema-specific properties
this.playlist = [];
this.currentPlaylistIndex = -1;
this.videoLibrary = null;
// Playback modes - default to loop playlist
this.repeatMode = 'all'; // 'off', 'one', 'all'
console.log('🎬 PornCinema extends BaseVideoPlayer');
}
async initialize() {
console.log('🎬 Initializing Porn Cinema...');
try {
// Initialize stats tracking
this.initializeStatsTracking();
// Initialize video library
this.videoLibrary = new VideoLibrary(this);
await this.videoLibrary.loadVideoLibrary();
// Setup playlist UI event handlers
this.setupPlaylistHandlers();
// Initialize repeat button
this.updateRepeatButton();
console.log('✅ Porn Cinema initialized successfully');
} catch (error) {
console.error('🎬 ❌ Error initializing Porn Cinema:', error);
throw error;
}
}
selectVideo(video) {
console.log('🎬 Playing video:', video.name);
try {
this.loadVideo(video.path, true);
} catch (error) {
console.error('🎬 ❌ Error playing video:', error);
}
}
playVideo(video) {
console.log('🎬 Playing video via VideoLibrary:', video.name);
try {
// Track video start for stats
this.trackVideoStart(video);
// Update video title and info if elements exist
const videoTitle = document.getElementById('video-title');
const videoInfo = document.getElementById('video-info');
if (videoTitle) {
videoTitle.textContent = video.name || 'Unknown Title';
}
if (videoInfo) {
videoInfo.textContent = `${video.category || 'Video'} • ${video.duration || 'Unknown duration'}`;
}
// Use BaseVideoPlayer's loadVideo method
this.loadVideo(video.path, true);
} catch (error) {
console.error('🎬 ❌ Error playing video:', error);
}
}
async addToPlaylist(video) {
console.log('🎬 Adding to playlist:', video.name);
try {
// Add video to playlist if not already present
if (!this.playlist.find(v => v.path === video.path)) {
this.playlist.push(video);
console.log(`✅ Added to playlist: ${video.name}`);
// Track stats
this.trackVideoAddedToPlaylist(video);
// Update playlist display
this.updatePlaylistDisplay();
// Show notification
this.showPlaylistNotification(`Added "${video.name}" to playlist`);
} else {
console.log(`ℹ️ Video already in playlist: ${video.name}`);
this.showPlaylistNotification(`"${video.name}" is already in playlist`);
}
} catch (error) {
console.error('🎬 ❌ Error adding to playlist:', error);
}
}
updatePlaylistDisplay() {
const playlistContent = document.getElementById('playlist-content');
if (!playlistContent) return;
if (this.playlist.length === 0) {
playlistContent.innerHTML = `
Playlist is empty. Add videos by clicking ➕ or pressing Enter while a video is selected.
`;
return;
}
playlistContent.innerHTML = `
${this.playlist.map((video, index) => this.createPlaylistItem(video, index)).join('')}
`;
// Attach event listeners to playlist items
this.attachPlaylistItemEvents();
}
createPlaylistItem(video, index) {
const isCurrentVideo = index === this.currentPlaylistIndex;
const duration = this.formatDuration(video.duration || 0);
const size = this.formatFileSize(video.size || 0);
return `
${video.name}
${duration} • ${size}
`;
}
attachPlaylistItemEvents() {
// Remove old event listeners by using event delegation instead of individual listeners
// This prevents memory leaks from accumulating listeners
const playlistContent = document.getElementById('playlist-content');
if (!playlistContent) return;
// Remove old delegated listener if it exists
if (this.playlistDelegateHandler) {
playlistContent.removeEventListener('click', this.playlistDelegateHandler);
}
// Create new delegated event handler
this.playlistDelegateHandler = (e) => {
const playButton = e.target.closest('.btn-playlist-play');
const removeButton = e.target.closest('.btn-playlist-remove');
const playlistItem = e.target.closest('.playlist-item');
if (playButton) {
e.stopPropagation();
const index = parseInt(playButton.dataset.index);
this.playFromPlaylist(index);
} else if (removeButton) {
e.stopPropagation();
const index = parseInt(removeButton.dataset.index);
this.removeFromPlaylist(index);
} else if (playlistItem && !e.target.closest('.playlist-item-actions')) {
const index = parseInt(playlistItem.dataset.index);
this.playFromPlaylist(index);
}
};
// Add single delegated event listener
playlistContent.addEventListener('click', this.playlistDelegateHandler);
}
playFromPlaylist(index) {
if (index < 0 || index >= this.playlist.length) return;
const video = this.playlist[index];
this.currentPlaylistIndex = index;
console.log(`🎬 Playing from playlist [${index}]: ${video.name}`);
this.playVideo(video);
this.updatePlaylistDisplay(); // Update to show current video
}
removeFromPlaylist(index) {
if (index < 0 || index >= this.playlist.length) return;
const video = this.playlist[index];
this.playlist.splice(index, 1);
// Adjust current playlist index if needed
if (this.currentPlaylistIndex > index) {
this.currentPlaylistIndex--;
} else if (this.currentPlaylistIndex === index) {
this.currentPlaylistIndex = -1; // Current video was removed
}
console.log(`🎬 Removed from playlist: ${video.name}`);
this.updatePlaylistDisplay();
this.showPlaylistNotification(`Removed "${video.name}" from playlist`);
}
setupPlaylistHandlers() {
// Repeat mode button
const repeatBtn = document.getElementById('repeat-mode-btn');
if (repeatBtn) {
repeatBtn.addEventListener('click', () => this.toggleRepeatMode());
}
// Clear playlist button
const clearBtn = document.getElementById('clear-playlist');
if (clearBtn) {
clearBtn.addEventListener('click', () => this.clearPlaylist());
}
// Shuffle playlist button
const shuffleBtn = document.getElementById('shuffle-playlist');
if (shuffleBtn) {
shuffleBtn.addEventListener('click', () => this.shufflePlaylist());
}
// Save playlist button (placeholder for now)
const saveBtn = document.getElementById('save-playlist');
if (saveBtn) {
saveBtn.addEventListener('click', () => this.savePlaylist());
}
// Load playlist button (placeholder for now)
const loadBtn = document.getElementById('load-playlist');
if (loadBtn) {
loadBtn.addEventListener('click', () => this.loadPlaylist());
}
}
clearPlaylist() {
this.playlist = [];
this.currentPlaylistIndex = -1;
this.updatePlaylistDisplay();
this.showPlaylistNotification('Playlist cleared');
console.log('🎬 Playlist cleared');
}
shufflePlaylist() {
if (this.playlist.length < 2) {
this.showPlaylistNotification('Need at least 2 videos to shuffle');
return;
}
// Fisher-Yates shuffle algorithm
for (let i = this.playlist.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[this.playlist[i], this.playlist[j]] = [this.playlist[j], this.playlist[i]];
}
this.currentPlaylistIndex = -1; // Reset current index after shuffle
this.updatePlaylistDisplay();
this.showPlaylistNotification('Playlist shuffled');
console.log('🎬 Playlist shuffled');
}
toggleRepeatMode() {
const modes = ['off', 'one', 'all'];
const currentIndex = modes.indexOf(this.repeatMode);
this.repeatMode = modes[(currentIndex + 1) % modes.length];
this.updateRepeatButton();
this.showPlaylistNotification(`Repeat: ${this.getRepeatModeLabel()}`);
console.log(`🎬 Repeat mode: ${this.repeatMode}`);
}
getRepeatModeLabel() {
switch (this.repeatMode) {
case 'off': return 'Off';
case 'one': return 'Current Video';
case 'all': return 'All Videos';
default: return 'Off';
}
}
updateRepeatButton() {
const repeatBtn = document.getElementById('repeat-mode-btn');
if (repeatBtn) {
let icon, title;
switch (this.repeatMode) {
case 'off':
icon = '🔁';
title = 'Repeat: Off';
repeatBtn.style.opacity = '0.5';
break;
case 'one':
icon = '🔂';
title = 'Repeat: Current Video';
repeatBtn.style.opacity = '1';
break;
case 'all':
icon = '🔁';
title = 'Repeat: All Videos';
repeatBtn.style.opacity = '1';
break;
}
repeatBtn.textContent = icon;
repeatBtn.title = title;
}
}
playNextVideo() {
console.log(`🎬 playNextVideo called - playlist length: ${this.playlist.length}, current index: ${this.currentPlaylistIndex}`);
if (this.playlist.length === 0) {
console.log('🎬 No playlist to continue');
return false;
}
let nextIndex = this.currentPlaylistIndex + 1;
console.log(`🎬 Next index would be: ${nextIndex}, repeat mode: ${this.repeatMode}`);
// Handle end of playlist
if (nextIndex >= this.playlist.length) {
if (this.repeatMode === 'all') {
nextIndex = 0; // Loop back to start
console.log('🎬 Looping back to start of playlist');
} else {
console.log('🎬 End of playlist reached');
return false;
}
}
console.log(`🎬 Playing video at index ${nextIndex}: ${this.playlist[nextIndex]?.name}`);
this.playFromPlaylist(nextIndex);
return true;
}
async savePlaylist() {
if (this.playlist.length === 0) {
this.showPlaylistNotification('Cannot save empty playlist');
return;
}
// Show save dialog to get playlist name
this.showSavePlaylistDialog();
}
showSavePlaylistDialog() {
// Create modal dialog for saving
const modal = document.createElement('div');
modal.className = 'playlist-modal';
modal.innerHTML = `
`;
// Add modal styles
modal.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 10002;
backdrop-filter: blur(5px);
`;
document.body.appendChild(modal);
// Focus on input and select text
const input = modal.querySelector('#playlist-name-input');
setTimeout(() => {
input.focus();
input.select();
}, 100);
// Setup event handlers
this.setupSaveModalEvents(modal);
}
setupSaveModalEvents(modal) {
const input = modal.querySelector('#playlist-name-input');
const saveBtn = modal.querySelector('.btn-save-playlist');
const saveExportBtn = modal.querySelector('.btn-save-and-export');
const closeBtn = modal.querySelector('.playlist-modal-close');
// Close modal handlers
const closeModal = () => {
document.body.removeChild(modal);
};
closeBtn.addEventListener('click', closeModal);
modal.addEventListener('click', (e) => {
if (e.target === modal) closeModal();
});
// Save handlers
const savePlaylist = async (shouldExport = false) => {
const name = input.value.trim();
if (!name) {
input.focus();
return;
}
try {
const playlistData = {
name: name,
created: new Date().toISOString(),
videos: this.playlist.map(video => ({
name: video.name,
path: video.path,
duration: video.duration,
size: video.size,
type: video.type
})),
count: this.playlist.length
};
// Save to localStorage
const savedPlaylists = this.getSavedPlaylists();
// Check for duplicate names
const existingIndex = savedPlaylists.findIndex(p => p.name === name);
if (existingIndex >= 0) {
// Replace existing playlist
savedPlaylists[existingIndex] = playlistData;
this.showPlaylistNotification(`Updated playlist: ${name}`);
} else {
// Add new playlist
savedPlaylists.push(playlistData);
this.showPlaylistNotification(`Saved playlist: ${name}`);
}
localStorage.setItem('pornCinema_savedPlaylists', JSON.stringify(savedPlaylists));
// Track playlist creation stats
this.trackPlaylistCreated(playlistData);
// Export if requested
if (shouldExport) {
await this.downloadPlaylistFile(playlistData);
}
console.log('🎬 ✅ Playlist saved:', name);
closeModal();
} catch (error) {
console.error('🎬 ❌ Error saving playlist:', error);
this.showPlaylistNotification('Error saving playlist');
}
};
saveBtn.addEventListener('click', () => savePlaylist(false));
saveExportBtn.addEventListener('click', () => savePlaylist(true));
// Enter key to save
input.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
savePlaylist(false);
}
});
}
async loadPlaylist() {
try {
const savedPlaylists = this.getSavedPlaylists();
if (savedPlaylists.length === 0) {
// No saved playlists, offer file upload
this.showPlaylistLoadDialog();
return;
}
// Show playlist selection dialog
this.showPlaylistSelectionDialog(savedPlaylists);
} catch (error) {
console.error('🎬 ❌ Error loading playlist:', error);
this.showPlaylistNotification('Error loading playlist');
}
}
getSavedPlaylists() {
try {
const saved = localStorage.getItem('pornCinema_savedPlaylists');
return saved ? JSON.parse(saved) : [];
} catch (error) {
console.error('🎬 ❌ Error getting saved playlists:', error);
return [];
}
}
async downloadPlaylistFile(playlistData) {
const dataStr = JSON.stringify(playlistData, null, 2);
const dataBlob = new Blob([dataStr], { type: 'application/json' });
const url = URL.createObjectURL(dataBlob);
const link = document.createElement('a');
link.href = url;
link.download = `${playlistData.name}.json`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
}
showPlaylistSelectionDialog(savedPlaylists) {
// Create modal dialog
const modal = document.createElement('div');
modal.className = 'playlist-modal';
modal.innerHTML = `
${savedPlaylists.map((playlist, index) => `
${playlist.name}
${playlist.count} videos • ${new Date(playlist.created).toLocaleDateString()}
`).join('')}
Or load from file
`;
// Add modal styles
modal.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 10002;
backdrop-filter: blur(5px);
`;
document.body.appendChild(modal);
// Add event listeners
this.setupPlaylistModalEvents(modal, savedPlaylists);
}
showPlaylistLoadDialog() {
// Simple file upload dialog
const input = document.createElement('input');
input.type = 'file';
input.accept = '.json';
input.style.display = 'none';
input.addEventListener('change', (e) => {
const file = e.target.files[0];
if (file) {
this.loadPlaylistFromFile(file);
}
});
document.body.appendChild(input);
input.click();
document.body.removeChild(input);
}
setupPlaylistModalEvents(modal, savedPlaylists) {
// Close modal
const closeBtn = modal.querySelector('.playlist-modal-close');
closeBtn.addEventListener('click', () => {
document.body.removeChild(modal);
});
// Click outside to close
modal.addEventListener('click', (e) => {
if (e.target === modal) {
document.body.removeChild(modal);
}
});
// Load playlist buttons
const loadBtns = modal.querySelectorAll('.btn-load-playlist');
loadBtns.forEach(btn => {
btn.addEventListener('click', () => {
const index = parseInt(btn.dataset.index);
this.loadSavedPlaylist(savedPlaylists[index]);
document.body.removeChild(modal);
});
});
// Export playlist buttons
const exportBtns = modal.querySelectorAll('.btn-export-playlist');
exportBtns.forEach(btn => {
btn.addEventListener('click', async () => {
const index = parseInt(btn.dataset.index);
await this.downloadPlaylistFile(savedPlaylists[index]);
this.showPlaylistNotification(`Exported playlist: ${savedPlaylists[index].name}`);
});
});
// Delete playlist buttons
const deleteBtns = modal.querySelectorAll('.btn-delete-playlist');
deleteBtns.forEach(btn => {
btn.addEventListener('click', () => {
const index = parseInt(btn.dataset.index);
this.deleteSavedPlaylist(index);
document.body.removeChild(modal);
// Refresh the dialog
const updatedPlaylists = this.getSavedPlaylists();
if (updatedPlaylists.length > 0) {
this.showPlaylistSelectionDialog(updatedPlaylists);
}
});
});
// File upload
const uploadBtn = modal.querySelector('.btn-upload-playlist');
const fileInput = modal.querySelector('#playlist-file-input');
uploadBtn.addEventListener('click', () => {
fileInput.click();
});
fileInput.addEventListener('change', (e) => {
const file = e.target.files[0];
if (file) {
this.loadPlaylistFromFile(file);
document.body.removeChild(modal);
}
});
}
loadSavedPlaylist(playlistData) {
try {
this.playlist = playlistData.videos || [];
this.currentPlaylistIndex = -1;
this.updatePlaylistDisplay();
this.showPlaylistNotification(`Loaded playlist: ${playlistData.name} (${this.playlist.length} videos)`);
console.log('🎬 ✅ Playlist loaded:', playlistData.name);
} catch (error) {
console.error('🎬 ❌ Error loading saved playlist:', error);
this.showPlaylistNotification('Error loading playlist');
}
}
async loadPlaylistFromFile(file) {
try {
const text = await file.text();
const playlistData = JSON.parse(text);
// Validate playlist data
if (!playlistData.videos || !Array.isArray(playlistData.videos)) {
throw new Error('Invalid playlist format');
}
this.loadSavedPlaylist(playlistData);
} catch (error) {
console.error('🎬 ❌ Error loading playlist from file:', error);
this.showPlaylistNotification('Error loading playlist file');
}
}
deleteSavedPlaylist(index) {
try {
const savedPlaylists = this.getSavedPlaylists();
const deleted = savedPlaylists.splice(index, 1)[0];
localStorage.setItem('pornCinema_savedPlaylists', JSON.stringify(savedPlaylists));
this.showPlaylistNotification(`Deleted playlist: ${deleted.name}`);
console.log('🎬 ✅ Playlist deleted:', deleted.name);
} catch (error) {
console.error('🎬 ❌ Error deleting playlist:', error);
this.showPlaylistNotification('Error deleting playlist');
}
}
showPlaylistNotification(message) {
// Create a simple notification system
const notification = document.createElement('div');
notification.className = 'playlist-notification';
notification.textContent = message;
notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 10px 15px;
border-radius: 5px;
z-index: 10001;
font-size: 14px;
backdrop-filter: blur(5px);
border: 1px solid rgba(255, 255, 255, 0.1);
`;
document.body.appendChild(notification);
// Remove after 3 seconds
setTimeout(() => {
if (notification.parentNode) {
notification.parentNode.removeChild(notification);
}
}, 3000);
}
formatDuration(seconds) {
if (!seconds || seconds === 0) return '--:--';
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const secs = Math.floor(seconds % 60);
if (hours > 0) {
return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
} else {
return `${minutes}:${secs.toString().padStart(2, '0')}`;
}
}
formatFileSize(bytes) {
if (!bytes || bytes === 0) return '--';
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(1024));
return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${sizes[i]}`;
}
// ===== STATS TRACKING METHODS =====
initializeStatsTracking() {
// Initialize global stats tracker if not already done
if (!window.playerStats) {
window.playerStats = new PlayerStats();
}
this.stats = window.playerStats;
console.log('📊 Stats tracking initialized for PornCinema');
}
trackVideoStart(video) {
if (this.stats) {
this.stats.onVideoStart(video);
}
}
trackVideoPlay() {
if (this.stats) {
this.stats.onVideoPlay();
}
}
trackVideoPause() {
if (this.stats) {
this.stats.onVideoPause();
}
}
trackVideoEnd(completionPercentage) {
if (this.stats) {
this.stats.onVideoEnd(completionPercentage);
}
}
trackPlaylistCreated(playlist) {
if (this.stats) {
this.stats.onPlaylistCreated(playlist);
}
}
trackVideoAddedToPlaylist(video) {
if (this.stats) {
this.stats.onVideoAddedToPlaylist(video);
}
}
// ===== OVERRIDE BASEVIDEOPLAYER EVENTS FOR STATS =====
onPlay() {
super.onPlay(); // Call parent method
this.trackVideoPlay();
}
onPause() {
super.onPause(); // Call parent method
this.trackVideoPause();
}
onEnded() {
console.log('🎬 Video ended - starting onEnded handler');
// Calculate completion percentage before calling parent
let completionPercentage = 100;
if (this.videoElement && this.videoElement.duration > 0) {
completionPercentage = (this.videoElement.currentTime / this.videoElement.duration) * 100;
}
super.onEnded(); // Call parent method
this.trackVideoEnd(completionPercentage);
console.log(`🎬 Repeat mode: ${this.repeatMode}`);
// Handle repeat and autoplay
if (this.repeatMode === 'one') {
// Repeat current video
console.log('🎬 Repeating current video');
this.videoElement.currentTime = 0;
this.play();
} else {
// Try to play next video in playlist
console.log('🎬 Attempting to play next video...');
if (!this.playNextVideo()) {
console.log('🎬 Playback finished - no more videos');
}
}
}
// Cleanup method to prevent memory leaks
destroy() {
console.log('🎬 Cleaning up Porn Cinema...');
try {
// Remove delegated event handler
const playlistContent = document.getElementById('playlist-content');
if (playlistContent && this.playlistDelegateHandler) {
playlistContent.removeEventListener('click', this.playlistDelegateHandler);
this.playlistDelegateHandler = null;
}
// Stop and unload video
if (this.videoElement) {
this.videoElement.pause();
this.videoElement.src = '';
this.videoElement.load();
}
// Clear playlist
this.playlist = [];
this.currentPlaylistIndex = -1;
// Destroy video library if it exists
if (this.videoLibrary && typeof this.videoLibrary.destroy === 'function') {
this.videoLibrary.destroy();
}
console.log('✅ Porn Cinema cleanup complete');
} catch (error) {
console.error('❌ Error during Porn Cinema cleanup:', error);
}
}
}
window.PornCinema = PornCinema;