Billy Barrow 1 месяц назад
Родитель
Сommit
7275eac1ed
3 измененных файлов с 654 добавлено и 642 удалено
  1. 0 634
      custom-controls.js
  2. 642 0
      ppvm_player.js
  3. 12 8
      video_player.php

+ 0 - 634
custom-controls.js

@@ -1,634 +0,0 @@
-// @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3-or-Later
-
-/**
- * CustomVideoControls object to handle custom video player functionality
- */
-const CustomVideoControls = {
-    video: null,
-    controls: null,
-    topControls: null,
-    playPauseBtn: null,
-    seekSlider: null,
-    progressBar: null,
-    progressFilled: null,
-    progressBuffered: null,
-    timeDisplay: null,
-    muteBtn: null,
-    volumeSlider: null,
-    fullscreenBtn: null,
-    
-    controlsTimeout: null,
-    mouseTimeout: null,
-    container: null,
-    isMouseOverContainer: false,
-    
-    /**
-     * Initialize the custom video controls
-     */
-    init() {
-        // Get DOM elements
-        this.video = document.getElementById('player');
-        this.controls = document.getElementById('custom-controls');
-        this.topControls = document.getElementById('controls');
-        this.playPauseBtn = document.getElementById('play-pause-btn');
-        this.seekSlider = document.getElementById('seek-slider');
-        this.progressBar = document.getElementById('progress-bar');
-        this.progressFilled = document.getElementById('progress-filled');
-        this.progressBuffered = document.getElementById('progress-buffered');
-        this.timeDisplay = document.getElementById('time-display');
-        this.muteBtn = document.getElementById('mute-btn');
-        this.volumeSlider = document.getElementById('volume-slider');
-        this.fullscreenBtn = document.getElementById('fullscreen-btn');
-        
-        if (!this.video || !this.controls) {
-            console.error('Custom video controls: Required elements not found');
-            return;
-        }
-        
-        // Create a container for video and controls if it doesn't exist
-        this.createVideoContainer();
-        
-        // Set up event listeners
-        this.setupEventListeners();
-        
-        // Initialize controls state
-        this.updatePlayPauseButton();
-        this.updateVolume();
-        this.updateTimeDisplay();
-        
-        // Show controls initially
-        this.showControls();
-        
-        // Set up mouse movement detection for auto-hiding controls
-        this.setupAutoHide();
-        
-        // Set up mouse inactivity detection
-        this.setupMouseInactivityDetection();
-        
-        // Set up focus handling for top controls
-        this.setupTopControlsFocusHandling();
-    },
-    
-    /**
-     * Create a container wrapper for video and controls for proper fullscreen handling
-     */
-    createVideoContainer() {
-        // Container already exists in HTML, just get reference
-        this.container = document.getElementById('video-container');
-        if (!this.container) {
-            console.error('Video container not found in HTML');
-            return;
-        }
-    },
-    
-    /**
-     * Set up all event listeners for the custom controls
-     */
-    setupEventListeners() {
-        // Play/Pause button
-        this.playPauseBtn.addEventListener('click', () => {
-            this.togglePlayPause();
-            this.playPauseBtn.blur();
-        });
-        
-        // Seek slider
-        this.seekSlider.addEventListener('input', () => {
-            this.seek();
-        });
-        this.seekSlider.addEventListener('change', () => {
-            this.seekSlider.blur();
-        });
-        
-        // Progress bar click to seek
-        this.progressBar.addEventListener('click', (e) => {
-            this.progressBarSeek(e);
-            e.target.blur();
-        });
-        
-        // Mute button
-        this.muteBtn.addEventListener('click', () => {
-            this.toggleMute();
-            this.muteBtn.blur();
-        });
-        
-        // Volume slider
-        this.volumeSlider.addEventListener('input', () => {
-            this.updateVolume();
-        });
-        this.volumeSlider.addEventListener('change', () => {
-            this.volumeSlider.blur();
-        });
-        
-        // Fullscreen button
-        this.fullscreenBtn.addEventListener('click', () => {
-            this.toggleFullscreen();
-            this.fullscreenBtn.blur();
-        });
-        
-        // Video click to toggle play/pause
-        this.video.addEventListener('click', (e) => {
-            // Prevent triggering if clicking on controls or if it's a right-click
-            if (e.target === this.video && e.button === 0) {
-                this.togglePlayPause();
-            }
-        });
-        
-        // Video events
-        this.video.addEventListener('timeupdate', () => this.updateProgress());
-        this.video.addEventListener('loadedmetadata', () => this.updateTimeDisplay());
-        this.video.addEventListener('volumechange', () => this.updateVolumeDisplay());
-        this.video.addEventListener('play', () => {
-            this.updatePlayPauseButton();
-            this.hideControlsWithTimeout(); // Auto-hide controls when playing
-            this.hideCursorWithTimeout(); // Auto-hide cursor when playing
-        });
-        this.video.addEventListener('pause', () => {
-            this.updatePlayPauseButton();
-            this.showControls(); // Show controls when paused
-            this.showCursor(); // Show cursor when paused
-            this.resetMouseTimeout(); // Clear cursor timeout when paused
-        });
-        this.video.addEventListener('seeking', () => {
-            this.showControls(); // Show controls when seeking
-        });
-        this.video.addEventListener('seeked', () => {
-            this.hideControlsWithTimeout(); // Auto-hide after seeking
-            this.hideCursorWithTimeout(); // Auto-hide cursor after seeking
-        });
-        
-        // Keyboard controls
-        document.addEventListener('keydown', (e) => this.handleKeyboard(e));
-        
-        // Controls visibility
-        this.controls.addEventListener('mouseenter', () => this.showControls());
-        this.controls.addEventListener('mouseleave', () => this.hideControls());
-        this.video.addEventListener('mouseenter', () => this.showControls());
-        this.video.addEventListener('mouseleave', () => this.hideControls());
-        this.video.addEventListener('mousemove', () => this.showControls());
-        
-        // Add mouse movement detection for the entire container
-        if (this.container) {
-            this.container.addEventListener('mousemove', () => this.handleMouseMove());
-            this.container.addEventListener('mouseleave', () => this.handleMouseLeave());
-            this.container.addEventListener('mouseenter', () => this.handleMouseEnter());
-        }
-        
-        // Touch events for mobile support
-        this.video.addEventListener('touchstart', (e) => {
-            this.showControls();
-            // Prevent default to avoid interfering with video controls
-            if (e.touches.length === 1) {
-                const touch = e.touches[0];
-                const rect = this.video.getBoundingClientRect();
-                // Only toggle play/pause if tapping in the center area
-                if (touch.clientX > rect.left + rect.width * 0.3 &&
-                    touch.clientX < rect.left + rect.width * 0.7 &&
-                    touch.clientY > rect.top + rect.height * 0.3 &&
-                    touch.clientY < rect.top + rect.height * 0.7) {
-                    this.togglePlayPause();
-                }
-            }
-        });
-        
-        this.controls.addEventListener('touchstart', () => {
-            this.showControls();
-        });
-        
-        // Handle window resize for responsive behavior
-        window.addEventListener('resize', () => {
-            this.handleResize();
-        });
-        
-        // Fullscreen change events
-        document.addEventListener('fullscreenchange', () => this.handleFullscreenChange());
-        document.addEventListener('webkitfullscreenchange', () => this.handleFullscreenChange());
-        document.addEventListener('mozfullscreenchange', () => this.handleFullscreenChange());
-        document.addEventListener('MSFullscreenChange', () => this.handleFullscreenChange());
-    },
-    
-    /**
-     * Toggle between play and pause
-     */
-    togglePlayPause() {
-        if (this.video.paused) {
-            this.video.play();
-        } else {
-            this.video.pause();
-        }
-    },
-    
-    /**
-     * Update the play/pause button display
-     */
-    updatePlayPauseButton() {
-        if (this.video.paused) {
-            this.playPauseBtn.textContent = '▶';
-            this.playPauseBtn.setAttribute('aria-label', 'Play');
-        } else {
-            this.playPauseBtn.textContent = '❚❚';
-            this.playPauseBtn.setAttribute('aria-label', 'Pause');
-        }
-    },
-    
-    /**
-     * Seek to the position specified by the seek slider
-     */
-    seek() {
-        const seekTime = this.seekSlider.value * this.video.duration / 100;
-        this.video.currentTime = seekTime;
-    },
-    
-    /**
-     * Handle clicking on the progress bar to seek
-     */
-    progressBarSeek(e) {
-        const rect = this.progressBar.getBoundingClientRect();
-        const pos = (e.clientX - rect.left) / rect.width;
-        this.video.currentTime = pos * this.video.duration;
-    },
-    
-    /**
-     * Update the progress bar and time display
-     */
-    updateProgress() {
-        const percentage = (this.video.currentTime / this.video.duration) * 100;
-        this.progressFilled.style.width = `${percentage}%`;
-        this.seekSlider.value = percentage;
-        
-        // Update buffer indicator
-        if (this.video.buffered.length > 0) {
-            const bufferedEnd = this.video.buffered.end(this.video.buffered.length - 1);
-            const bufferedPercentage = (bufferedEnd / this.video.duration) * 100;
-            this.progressBuffered.style.width = `${bufferedPercentage}%`;
-        }
-        
-        this.updateTimeDisplay();
-    },
-    
-    /**
-     * Update the time display
-     */
-    updateTimeDisplay() {
-        const currentTime = this.formatTime(this.video.currentTime);
-        const duration = this.formatTime(this.video.duration);
-        this.timeDisplay.textContent = `${currentTime} / ${duration}`;
-    },
-    
-    /**
-     * Format time in seconds to MM:SS or HH:MM:SS format
-     */
-    formatTime(seconds) {
-        if (isNaN(seconds)) return '0:00';
-        
-        const h = Math.floor(seconds / 3600);
-        const m = Math.floor((seconds % 3600) / 60);
-        const s = Math.floor(seconds % 60);
-        
-        if (h > 0) {
-            return `${h}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
-        } else {
-            return `${m}:${s.toString().padStart(2, '0')}`;
-        }
-    },
-    
-    /**
-     * Toggle mute state
-     */
-    toggleMute() {
-        this.video.muted = !this.video.muted;
-    },
-    
-    /**
-     * Update volume based on slider value
-     */
-    updateVolume() {
-        this.video.volume = this.volumeSlider.value;
-    },
-    
-    /**
-     * Update volume display (mute button)
-     */
-    updateVolumeDisplay() {
-        if (this.video.muted || this.video.volume === 0) {
-            this.muteBtn.textContent = '🔇';
-            this.muteBtn.setAttribute('aria-label', 'Unmute');
-        } else if (this.video.volume < 0.5) {
-            this.muteBtn.textContent = '🔉';
-            this.muteBtn.setAttribute('aria-label', 'Mute');
-        } else {
-            this.muteBtn.textContent = '🔊';
-            this.muteBtn.setAttribute('aria-label', 'Mute');
-        }
-    },
-    
-    /**
-     * Toggle fullscreen mode
-     */
-    toggleFullscreen() {
-        if (!document.fullscreenElement) {
-            // Request fullscreen for the container instead of just the video
-            if (this.container.requestFullscreen) {
-                this.container.requestFullscreen();
-            } else if (this.container.webkitRequestFullscreen) { /* Safari */
-                this.container.webkitRequestFullscreen();
-            } else if (this.container.mozRequestFullScreen) { /* Firefox */
-                this.container.mozRequestFullScreen();
-            } else if (this.container.msRequestFullscreen) { /* IE11 */
-                this.container.msRequestFullscreen();
-            }
-        } else {
-            if (document.exitFullscreen) {
-                document.exitFullscreen();
-            } else if (document.webkitExitFullscreen) { /* Safari */
-                document.webkitExitFullscreen();
-            } else if (document.mozCancelFullScreen) { /* Firefox */
-                document.mozCancelFullScreen();
-            } else if (document.msExitFullscreen) { /* IE11 */
-                document.msExitFullscreen();
-            }
-        }
-    },
-    
-    /**
-     * Handle fullscreen change events
-     */
-    handleFullscreenChange() {
-        const isFullscreen = !!(document.fullscreenElement ||
-                              document.webkitFullscreenElement ||
-                              document.mozFullScreenElement ||
-                              document.msFullscreenElement);
-        
-        if (isFullscreen) {
-            // Adjust controls for fullscreen
-            this.controls.style.position = 'absolute';
-            this.controls.style.bottom = '0';
-            this.controls.style.left = '0';
-            this.controls.style.right = '0';
-            this.controls.style.zIndex = '2147483647'; // Maximum z-index
-            
-            // Make video fill the container
-            this.video.style.width = '100%';
-            this.video.style.height = '100%';
-            this.video.style.objectFit = 'contain';
-        } else {
-            // Reset styles when exiting fullscreen
-            this.controls.style.position = '';
-            this.controls.style.bottom = '';
-            this.controls.style.left = '';
-            this.controls.style.right = '';
-            this.controls.style.zIndex = '';
-            
-            this.video.style.width = '';
-            this.video.style.height = '';
-            this.video.style.objectFit = '';
-        }
-        
-        // Always show controls when entering/exiting fullscreen
-        this.showControls();
-        
-        // Auto-hide controls and cursor after a delay in fullscreen mode if video is playing
-        if (isFullscreen && !this.video.paused) {
-            this.hideControlsWithTimeout();
-            this.hideCursorWithTimeout();
-        }
-    },
-    
-    /**
-     * Handle keyboard controls
-     */
-    handleKeyboard(e) {
-        // Only handle keys when video is the active element or no input is focused
-        if (document.activeElement.tagName === 'INPUT' || document.activeElement.tagName === 'TEXTAREA') {
-            return;
-        }
-        
-        switch (e.key) {
-            case ' ':
-            case 'Spacebar':
-                e.preventDefault();
-                this.togglePlayPause();
-                break;
-            case 'ArrowLeft':
-                e.preventDefault();
-                this.video.currentTime -= 5;
-                break;
-            case 'ArrowRight':
-                e.preventDefault();
-                this.video.currentTime += 5;
-                break;
-            case 'ArrowUp':
-                e.preventDefault();
-                this.video.volume = Math.min(1, this.video.volume + 0.1);
-                this.volumeSlider.value = this.video.volume;
-                break;
-            case 'ArrowDown':
-                e.preventDefault();
-                this.video.volume = Math.max(0, this.video.volume - 0.1);
-                this.volumeSlider.value = this.video.volume;
-                break;
-            case 'm':
-            case 'M':
-                e.preventDefault();
-                this.toggleMute();
-                break;
-            case 'f':
-            case 'F':
-                e.preventDefault();
-                this.toggleFullscreen();
-                break;
-        }
-    },
-    
-    /**
-     * Show the controls
-     */
-    showControls() {
-        this.controls.classList.add('show');
-        if (this.topControls) {
-            this.topControls.classList.add('show');
-        }
-        this.showCursor();
-        
-        // Only reset timeout if video is playing (to allow auto-hide when idle)
-        if (!this.video.paused) {
-            this.resetControlsTimeout();
-            this.hideControlsWithTimeout();
-            this.resetMouseTimeout();
-            this.hideCursorWithTimeout();
-        }
-    },
-    
-    /**
-     * Hide the controls
-     */
-    hideControls() {
-        if (!this.video.paused) {
-            this.controls.classList.remove('show');
-            if (this.topControls) {
-                this.topControls.classList.remove('show');
-            }
-            this.hideCursor();
-        }
-    },
-    
-    /**
-     * Show the cursor
-     */
-    showCursor() {
-        if (this.container) {
-            this.container.style.cursor = 'auto';
-        }
-    },
-    
-    /**
-     * Hide the cursor
-     */
-    hideCursor() {
-        if (this.container && !this.video.paused) {
-            this.container.style.cursor = 'none';
-        }
-    },
-    
-    /**
-     * Hide cursor with a timeout
-     */
-    hideCursorWithTimeout() {
-        this.resetMouseTimeout();
-        this.mouseTimeout = setTimeout(() => {
-            this.hideCursor();
-        }, 3000); // 3 seconds
-    },
-    
-    /**
-     * Reset the mouse timeout
-     */
-    resetMouseTimeout() {
-        if (this.mouseTimeout) {
-            clearTimeout(this.mouseTimeout);
-            this.mouseTimeout = null;
-        }
-    },
-    
-    /**
-     * Handle mouse movement inside the container
-     */
-    handleMouseMove() {
-        this.showControls();
-        if (!this.video.paused) {
-            this.resetMouseTimeout();
-            this.hideCursorWithTimeout();
-        }
-    },
-    
-    /**
-     * Handle mouse entering the container
-     */
-    handleMouseEnter() {
-        this.isMouseOverContainer = true;
-        this.showControls();
-    },
-    
-    /**
-     * Handle mouse leaving the container
-     */
-    handleMouseLeave() {
-        this.isMouseOverContainer = false;
-        this.hideControls();
-    },
-    
-    /**
-     * Hide controls with a timeout
-     */
-    hideControlsWithTimeout() {
-        this.resetControlsTimeout();
-        this.controlsTimeout = setTimeout(() => {
-            this.hideControls();
-        }, 3000); // Changed to 3 seconds
-    },
-    
-    /**
-     * Reset the controls timeout
-     */
-    resetControlsTimeout() {
-        if (this.controlsTimeout) {
-            clearTimeout(this.controlsTimeout);
-            this.controlsTimeout = null;
-        }
-    },
-    
-    /**
-     * Set up auto-hide functionality for controls
-     */
-    setupAutoHide() {
-        // Note: These event listeners are already added in setupEventListeners()
-        // so we don't need to add them again here to prevent duplicate execution
-    },
-    
-    /**
-     * Set up mouse inactivity detection
-     */
-    setupMouseInactivityDetection() {
-        // Initial state - show cursor
-        this.showCursor();
-        
-        // Set up initial timeout if video is playing
-        if (!this.video.paused) {
-            this.hideCursorWithTimeout();
-        }
-    },
-    
-    /**
-     * Handle window resize events
-     */
-    handleResize() {
-        // Show controls during resize to ensure they're visible
-        this.showControls();
-        
-        // Auto-hide after resize is complete
-        clearTimeout(this.resizeTimeout);
-        this.resizeTimeout = setTimeout(() => {
-            if (!this.video.paused) {
-                this.hideControlsWithTimeout();
-                this.hideCursorWithTimeout();
-            }
-        }, 1000);
-    },
-    
-    /**
-     * Set up focus handling for top controls
-     */
-    setupTopControlsFocusHandling() {
-        // Get top control buttons and select
-        const infoBtn = document.querySelector('button[onclick="showInfo()"]');
-        const shareBtn = document.querySelector('button[onclick="shareVideo()"]');
-        const downloadBtn = document.querySelector('button[onclick="downloadVideo()"]');
-        const qualitySelector = document.getElementById('quality-selector');
-        
-        // Add blur() calls to top control buttons
-        if (infoBtn) {
-            infoBtn.addEventListener('click', () => {
-                infoBtn.blur();
-            });
-        }
-        
-        if (shareBtn) {
-            shareBtn.addEventListener('click', () => {
-                shareBtn.blur();
-            });
-        }
-        
-        if (downloadBtn) {
-            downloadBtn.addEventListener('click', () => {
-                downloadBtn.blur();
-            });
-        }
-        
-        if (qualitySelector) {
-            qualitySelector.addEventListener('change', () => {
-                qualitySelector.blur();
-            });
-        }
-    },
-};
-
-// @license-end

+ 642 - 0
ppvm_player.js

@@ -163,4 +163,646 @@ function doSpeedTest(currentOption, options) {
     request.send();
 }
 
+
+/**
+ * CustomVideoControls object to handle custom video player functionality
+ */
+const CustomVideoControls = {
+    video: null,
+    controls: null,
+    topControls: null,
+    playPauseBtn: null,
+    seekSlider: null,
+    progressBar: null,
+    progressFilled: null,
+    progressBuffered: null,
+    timeDisplay: null,
+    muteBtn: null,
+    volumeSlider: null,
+    fullscreenBtn: null,
+    
+    controlsTimeout: null,
+    mouseTimeout: null,
+    container: null,
+    isMouseOverContainer: false,
+    
+    /**
+     * Initialize the custom video controls
+     */
+    init() {
+        // Get DOM elements
+        this.video = document.getElementById('player');
+        this.controls = document.getElementById('custom-controls');
+        this.topControls = document.getElementById('controls');
+        this.playPauseBtn = document.getElementById('play-pause-btn');
+        this.seekSlider = document.getElementById('seek-slider');
+        this.progressBar = document.getElementById('progress-bar');
+        this.progressFilled = document.getElementById('progress-filled');
+        this.progressBuffered = document.getElementById('progress-buffered');
+        this.timeDisplay = document.getElementById('time-display');
+        this.muteBtn = document.getElementById('mute-btn');
+        this.volumeSlider = document.getElementById('volume-slider');
+        this.fullscreenBtn = document.getElementById('fullscreen-btn');
+        
+        if (!this.video || !this.controls) {
+            console.error('Custom video controls: Required elements not found');
+            return;
+        }
+
+        // Disable default video controls
+        if (this.video) {
+            this.video.controls = false;
+        }
+
+        // Remove noscript class from controls
+        if(this.controls) {
+            this.controls.classList.remove("noscript");
+        }
+        
+        // Create a container for video and controls if it doesn't exist
+        this.createVideoContainer();
+        
+        // Set up event listeners
+        this.setupEventListeners();
+        
+        // Initialize controls state
+        this.updatePlayPauseButton();
+        this.updateVolume();
+        this.updateTimeDisplay();
+        
+        // Show controls initially
+        this.showControls();
+        
+        // Set up mouse movement detection for auto-hiding controls
+        this.setupAutoHide();
+        
+        // Set up mouse inactivity detection
+        this.setupMouseInactivityDetection();
+        
+        // Set up focus handling for top controls
+        this.setupTopControlsFocusHandling();
+    },
+    
+    /**
+     * Create a container wrapper for video and controls for proper fullscreen handling
+     */
+    createVideoContainer() {
+        // Container already exists in HTML, just get reference
+        this.container = document.getElementById('video-container');
+        if (!this.container) {
+            console.error('Video container not found in HTML');
+            return;
+        }
+    },
+    
+    /**
+     * Set up all event listeners for the custom controls
+     */
+    setupEventListeners() {
+        // Play/Pause button
+        this.playPauseBtn.addEventListener('click', () => {
+            this.togglePlayPause();
+            this.playPauseBtn.blur();
+        });
+        
+        // Seek slider
+        this.seekSlider.addEventListener('input', () => {
+            this.seek();
+        });
+        this.seekSlider.addEventListener('change', () => {
+            this.seekSlider.blur();
+        });
+        
+        // Progress bar click to seek
+        this.progressBar.addEventListener('click', (e) => {
+            this.progressBarSeek(e);
+            e.target.blur();
+        });
+        
+        // Mute button
+        this.muteBtn.addEventListener('click', () => {
+            this.toggleMute();
+            this.muteBtn.blur();
+        });
+        
+        // Volume slider
+        this.volumeSlider.addEventListener('input', () => {
+            this.updateVolume();
+        });
+        this.volumeSlider.addEventListener('change', () => {
+            this.volumeSlider.blur();
+        });
+        
+        // Fullscreen button
+        this.fullscreenBtn.addEventListener('click', () => {
+            this.toggleFullscreen();
+            this.fullscreenBtn.blur();
+        });
+        
+        // Video click to toggle play/pause
+        this.video.addEventListener('click', (e) => {
+            // Prevent triggering if clicking on controls or if it's a right-click
+            if (e.target === this.video && e.button === 0) {
+                this.togglePlayPause();
+            }
+        });
+        
+        // Video events
+        this.video.addEventListener('timeupdate', () => this.updateProgress());
+        this.video.addEventListener('loadedmetadata', () => this.updateTimeDisplay());
+        this.video.addEventListener('volumechange', () => this.updateVolumeDisplay());
+        this.video.addEventListener('play', () => {
+            this.updatePlayPauseButton();
+            this.hideControlsWithTimeout(); // Auto-hide controls when playing
+            this.hideCursorWithTimeout(); // Auto-hide cursor when playing
+        });
+        this.video.addEventListener('pause', () => {
+            this.updatePlayPauseButton();
+            this.showControls(); // Show controls when paused
+            this.showCursor(); // Show cursor when paused
+            this.resetMouseTimeout(); // Clear cursor timeout when paused
+        });
+        this.video.addEventListener('seeking', () => {
+            this.showControls(); // Show controls when seeking
+        });
+        this.video.addEventListener('seeked', () => {
+            this.hideControlsWithTimeout(); // Auto-hide after seeking
+            this.hideCursorWithTimeout(); // Auto-hide cursor after seeking
+        });
+        
+        // Keyboard controls
+        document.addEventListener('keydown', (e) => this.handleKeyboard(e));
+        
+        // Controls visibility
+        this.controls.addEventListener('mouseenter', () => this.showControls());
+        this.controls.addEventListener('mouseleave', () => this.hideControls());
+        this.video.addEventListener('mouseenter', () => this.showControls());
+        this.video.addEventListener('mouseleave', () => this.hideControls());
+        this.video.addEventListener('mousemove', () => this.showControls());
+        
+        // Add mouse movement detection for the entire container
+        if (this.container) {
+            this.container.addEventListener('mousemove', () => this.handleMouseMove());
+            this.container.addEventListener('mouseleave', () => this.handleMouseLeave());
+            this.container.addEventListener('mouseenter', () => this.handleMouseEnter());
+        }
+        
+        // Touch events for mobile support
+        this.video.addEventListener('touchstart', (e) => {
+            this.showControls();
+            // Prevent default to avoid interfering with video controls
+            if (e.touches.length === 1) {
+                const touch = e.touches[0];
+                const rect = this.video.getBoundingClientRect();
+                // Only toggle play/pause if tapping in the center area
+                if (touch.clientX > rect.left + rect.width * 0.3 &&
+                    touch.clientX < rect.left + rect.width * 0.7 &&
+                    touch.clientY > rect.top + rect.height * 0.3 &&
+                    touch.clientY < rect.top + rect.height * 0.7) {
+                    this.togglePlayPause();
+                }
+            }
+        });
+        
+        this.controls.addEventListener('touchstart', () => {
+            this.showControls();
+        });
+        
+        // Handle window resize for responsive behavior
+        window.addEventListener('resize', () => {
+            this.handleResize();
+        });
+        
+        // Fullscreen change events
+        document.addEventListener('fullscreenchange', () => this.handleFullscreenChange());
+        document.addEventListener('webkitfullscreenchange', () => this.handleFullscreenChange());
+        document.addEventListener('mozfullscreenchange', () => this.handleFullscreenChange());
+        document.addEventListener('MSFullscreenChange', () => this.handleFullscreenChange());
+    },
+    
+    /**
+     * Toggle between play and pause
+     */
+    togglePlayPause() {
+        if (this.video.paused) {
+            this.video.play();
+        } else {
+            this.video.pause();
+        }
+    },
+    
+    /**
+     * Update the play/pause button display
+     */
+    updatePlayPauseButton() {
+        if (this.video.paused) {
+            this.playPauseBtn.textContent = '▶';
+            this.playPauseBtn.setAttribute('aria-label', 'Play');
+        } else {
+            this.playPauseBtn.textContent = '❚❚';
+            this.playPauseBtn.setAttribute('aria-label', 'Pause');
+        }
+    },
+    
+    /**
+     * Seek to the position specified by the seek slider
+     */
+    seek() {
+        const seekTime = this.seekSlider.value * this.video.duration / 100;
+        this.video.currentTime = seekTime;
+    },
+    
+    /**
+     * Handle clicking on the progress bar to seek
+     */
+    progressBarSeek(e) {
+        const rect = this.progressBar.getBoundingClientRect();
+        const pos = (e.clientX - rect.left) / rect.width;
+        this.video.currentTime = pos * this.video.duration;
+    },
+    
+    /**
+     * Update the progress bar and time display
+     */
+    updateProgress() {
+        const percentage = (this.video.currentTime / this.video.duration) * 100;
+        this.progressFilled.style.width = `${percentage}%`;
+        this.seekSlider.value = percentage;
+        
+        // Update buffer indicator
+        if (this.video.buffered.length > 0) {
+            const bufferedEnd = this.video.buffered.end(this.video.buffered.length - 1);
+            const bufferedPercentage = (bufferedEnd / this.video.duration) * 100;
+            this.progressBuffered.style.width = `${bufferedPercentage}%`;
+        }
+        
+        this.updateTimeDisplay();
+    },
+    
+    /**
+     * Update the time display
+     */
+    updateTimeDisplay() {
+        const currentTime = this.formatTime(this.video.currentTime);
+        const duration = this.formatTime(this.video.duration);
+        this.timeDisplay.textContent = `${currentTime} / ${duration}`;
+    },
+    
+    /**
+     * Format time in seconds to MM:SS or HH:MM:SS format
+     */
+    formatTime(seconds) {
+        if (isNaN(seconds)) return '0:00';
+        
+        const h = Math.floor(seconds / 3600);
+        const m = Math.floor((seconds % 3600) / 60);
+        const s = Math.floor(seconds % 60);
+        
+        if (h > 0) {
+            return `${h}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
+        } else {
+            return `${m}:${s.toString().padStart(2, '0')}`;
+        }
+    },
+    
+    /**
+     * Toggle mute state
+     */
+    toggleMute() {
+        this.video.muted = !this.video.muted;
+    },
+    
+    /**
+     * Update volume based on slider value
+     */
+    updateVolume() {
+        this.video.volume = this.volumeSlider.value;
+    },
+    
+    /**
+     * Update volume display (mute button)
+     */
+    updateVolumeDisplay() {
+        if (this.video.muted || this.video.volume === 0) {
+            this.muteBtn.textContent = '🔇';
+            this.muteBtn.setAttribute('aria-label', 'Unmute');
+        } else if (this.video.volume < 0.5) {
+            this.muteBtn.textContent = '🔉';
+            this.muteBtn.setAttribute('aria-label', 'Mute');
+        } else {
+            this.muteBtn.textContent = '🔊';
+            this.muteBtn.setAttribute('aria-label', 'Mute');
+        }
+    },
+    
+    /**
+     * Toggle fullscreen mode
+     */
+    toggleFullscreen() {
+        if (!document.fullscreenElement) {
+            // Request fullscreen for the container instead of just the video
+            if (this.container.requestFullscreen) {
+                this.container.requestFullscreen();
+            } else if (this.container.webkitRequestFullscreen) { /* Safari */
+                this.container.webkitRequestFullscreen();
+            } else if (this.container.mozRequestFullScreen) { /* Firefox */
+                this.container.mozRequestFullScreen();
+            } else if (this.container.msRequestFullscreen) { /* IE11 */
+                this.container.msRequestFullscreen();
+            }
+        } else {
+            if (document.exitFullscreen) {
+                document.exitFullscreen();
+            } else if (document.webkitExitFullscreen) { /* Safari */
+                document.webkitExitFullscreen();
+            } else if (document.mozCancelFullScreen) { /* Firefox */
+                document.mozCancelFullScreen();
+            } else if (document.msExitFullscreen) { /* IE11 */
+                document.msExitFullscreen();
+            }
+        }
+    },
+    
+    /**
+     * Handle fullscreen change events
+     */
+    handleFullscreenChange() {
+        const isFullscreen = !!(document.fullscreenElement ||
+                              document.webkitFullscreenElement ||
+                              document.mozFullScreenElement ||
+                              document.msFullscreenElement);
+        
+        if (isFullscreen) {
+            // Adjust controls for fullscreen
+            this.controls.style.position = 'absolute';
+            this.controls.style.bottom = '0';
+            this.controls.style.left = '0';
+            this.controls.style.right = '0';
+            this.controls.style.zIndex = '2147483647'; // Maximum z-index
+            
+            // Make video fill the container
+            this.video.style.width = '100%';
+            this.video.style.height = '100%';
+            this.video.style.objectFit = 'contain';
+        } else {
+            // Reset styles when exiting fullscreen
+            this.controls.style.position = '';
+            this.controls.style.bottom = '';
+            this.controls.style.left = '';
+            this.controls.style.right = '';
+            this.controls.style.zIndex = '';
+            
+            this.video.style.width = '';
+            this.video.style.height = '';
+            this.video.style.objectFit = '';
+        }
+        
+        // Always show controls when entering/exiting fullscreen
+        this.showControls();
+        
+        // Auto-hide controls and cursor after a delay in fullscreen mode if video is playing
+        if (isFullscreen && !this.video.paused) {
+            this.hideControlsWithTimeout();
+            this.hideCursorWithTimeout();
+        }
+    },
+    
+    /**
+     * Handle keyboard controls
+     */
+    handleKeyboard(e) {
+        // Only handle keys when video is the active element or no input is focused
+        if (document.activeElement.tagName === 'INPUT' || document.activeElement.tagName === 'TEXTAREA') {
+            return;
+        }
+        
+        switch (e.key) {
+            case ' ':
+            case 'Spacebar':
+                e.preventDefault();
+                this.togglePlayPause();
+                break;
+            case 'ArrowLeft':
+                e.preventDefault();
+                this.video.currentTime -= 5;
+                break;
+            case 'ArrowRight':
+                e.preventDefault();
+                this.video.currentTime += 5;
+                break;
+            case 'ArrowUp':
+                e.preventDefault();
+                this.video.volume = Math.min(1, this.video.volume + 0.1);
+                this.volumeSlider.value = this.video.volume;
+                break;
+            case 'ArrowDown':
+                e.preventDefault();
+                this.video.volume = Math.max(0, this.video.volume - 0.1);
+                this.volumeSlider.value = this.video.volume;
+                break;
+            case 'm':
+            case 'M':
+                e.preventDefault();
+                this.toggleMute();
+                break;
+            case 'f':
+            case 'F':
+                e.preventDefault();
+                this.toggleFullscreen();
+                break;
+        }
+    },
+    
+    /**
+     * Show the controls
+     */
+    showControls() {
+        this.controls.classList.add('show');
+        if (this.topControls) {
+            this.topControls.classList.add('show');
+        }
+        this.showCursor();
+        
+        // Only reset timeout if video is playing (to allow auto-hide when idle)
+        if (!this.video.paused) {
+            this.resetControlsTimeout();
+            this.hideControlsWithTimeout();
+            this.resetMouseTimeout();
+            this.hideCursorWithTimeout();
+        }
+    },
+    
+    /**
+     * Hide the controls
+     */
+    hideControls() {
+        if (!this.video.paused) {
+            this.controls.classList.remove('show');
+            if (this.topControls) {
+                this.topControls.classList.remove('show');
+            }
+            this.hideCursor();
+        }
+    },
+    
+    /**
+     * Show the cursor
+     */
+    showCursor() {
+        if (this.container) {
+            this.container.style.cursor = 'auto';
+        }
+    },
+    
+    /**
+     * Hide the cursor
+     */
+    hideCursor() {
+        if (this.container && !this.video.paused) {
+            this.container.style.cursor = 'none';
+        }
+    },
+    
+    /**
+     * Hide cursor with a timeout
+     */
+    hideCursorWithTimeout() {
+        this.resetMouseTimeout();
+        this.mouseTimeout = setTimeout(() => {
+            this.hideCursor();
+        }, 3000); // 3 seconds
+    },
+    
+    /**
+     * Reset the mouse timeout
+     */
+    resetMouseTimeout() {
+        if (this.mouseTimeout) {
+            clearTimeout(this.mouseTimeout);
+            this.mouseTimeout = null;
+        }
+    },
+    
+    /**
+     * Handle mouse movement inside the container
+     */
+    handleMouseMove() {
+        this.showControls();
+        if (!this.video.paused) {
+            this.resetMouseTimeout();
+            this.hideCursorWithTimeout();
+        }
+    },
+    
+    /**
+     * Handle mouse entering the container
+     */
+    handleMouseEnter() {
+        this.isMouseOverContainer = true;
+        this.showControls();
+    },
+    
+    /**
+     * Handle mouse leaving the container
+     */
+    handleMouseLeave() {
+        this.isMouseOverContainer = false;
+        this.hideControls();
+    },
+    
+    /**
+     * Hide controls with a timeout
+     */
+    hideControlsWithTimeout() {
+        this.resetControlsTimeout();
+        this.controlsTimeout = setTimeout(() => {
+            this.hideControls();
+        }, 3000); // Changed to 3 seconds
+    },
+    
+    /**
+     * Reset the controls timeout
+     */
+    resetControlsTimeout() {
+        if (this.controlsTimeout) {
+            clearTimeout(this.controlsTimeout);
+            this.controlsTimeout = null;
+        }
+    },
+    
+    /**
+     * Set up auto-hide functionality for controls
+     */
+    setupAutoHide() {
+        // Note: These event listeners are already added in setupEventListeners()
+        // so we don't need to add them again here to prevent duplicate execution
+    },
+    
+    /**
+     * Set up mouse inactivity detection
+     */
+    setupMouseInactivityDetection() {
+        // Initial state - show cursor
+        this.showCursor();
+        
+        // Set up initial timeout if video is playing
+        if (!this.video.paused) {
+            this.hideCursorWithTimeout();
+        }
+    },
+    
+    /**
+     * Handle window resize events
+     */
+    handleResize() {
+        // Show controls during resize to ensure they're visible
+        this.showControls();
+        
+        // Auto-hide after resize is complete
+        clearTimeout(this.resizeTimeout);
+        this.resizeTimeout = setTimeout(() => {
+            if (!this.video.paused) {
+                this.hideControlsWithTimeout();
+                this.hideCursorWithTimeout();
+            }
+        }, 1000);
+    },
+    
+    /**
+     * Set up focus handling for top controls
+     */
+    setupTopControlsFocusHandling() {
+        // Get top control buttons and select
+        const infoBtn = document.querySelector('button[onclick="showInfo()"]');
+        const shareBtn = document.querySelector('button[onclick="shareVideo()"]');
+        const downloadBtn = document.querySelector('button[onclick="downloadVideo()"]');
+        const qualitySelector = document.getElementById('quality-selector');
+        
+        // Add blur() calls to top control buttons
+        if (infoBtn) {
+            infoBtn.addEventListener('click', () => {
+                infoBtn.blur();
+            });
+        }
+        
+        if (shareBtn) {
+            shareBtn.addEventListener('click', () => {
+                shareBtn.blur();
+            });
+        }
+        
+        if (downloadBtn) {
+            downloadBtn.addEventListener('click', () => {
+                downloadBtn.blur();
+            });
+        }
+        
+        if (qualitySelector) {
+            qualitySelector.addEventListener('change', () => {
+                qualitySelector.blur();
+            });
+        }
+    },
+};
+
 // @license-end

+ 12 - 8
video_player.php

@@ -20,7 +20,6 @@ function ppvm_player($ppub, $path, $video) {
         <meta name="author" content="<?php echo(htmlentities($metadata["author"]));?>">
         <link rel="alternate" type="application/x-ppub" title="<?php echo(htmlentities($metadata["title"]));?> (as PPUB)" href="?download=true" />
         <script type="text/javascript" src="<?php echo(SITE_URL);?>/ppvm_player.js"></script>
-        <script type="text/javascript" src="<?php echo(SITE_URL);?>/custom-controls.js"></script>
         <style type="text/css">
             body {
                 margin: 0px;
@@ -230,11 +229,14 @@ function ppvm_player($ppub, $path, $video) {
                 opacity: 1;
             }
 
-            /* Removed body:hover .video-controls to let JavaScript handle visibility */
             .video-controls:hover,
             .video-controls:focus-within {
                 opacity: 1;
             }
+
+            .video-controls.noscript {
+                display: none;
+            }
             
             /* Ensure controls are initially visible and properly positioned */
             #video-container .video-controls {
@@ -295,7 +297,7 @@ function ppvm_player($ppub, $path, $video) {
                 flex: 1;
                 position: relative;
                 height: 5px;
-                background-color: rgba(255, 255, 255, 0.3);
+                background-color: #000000;
                 border-radius: 2.5px;
                 cursor: pointer;
             }
@@ -304,7 +306,7 @@ function ppvm_player($ppub, $path, $video) {
                 position: absolute;
                 width: 100%;
                 height: 100%;
-                background-color: rgba(255, 255, 255, 0.3);
+                background-color: #000000;
                 border-radius: 2.5px;
             }
 
@@ -312,16 +314,18 @@ function ppvm_player($ppub, $path, $video) {
                 position: absolute;
                 width: 0%;
                 height: 100%;
-                background-color: skyblue;
+                background-color: #00b6f0;
                 border-radius: 2.5px;
+                z-index: 2;
             }
             
             .progress-buffered {
                 position: absolute;
                 width: 0%;
                 height: 100%;
-                background-color: rgba(135, 206, 250, 0.3);
+                background-color: #515151;
                 border-radius: 2.5px;
+                z-index: 1;
             }
 
             .seek-slider {
@@ -444,7 +448,7 @@ function ppvm_player($ppub, $path, $video) {
     <body>
 
         <div id="video-container">
-            <video id="player" poster="<?php echo($video->metadata["poster"]);?>" preload="metadata" src="<?php echo($video->metadata["master"]);?>">
+            <video id="player" poster="<?php echo($video->metadata["poster"]);?>" preload="metadata" src="<?php echo($video->metadata["master"]);?>" controls>
             </video>
             
             <!-- Top Controls (moved inside video container) -->
@@ -460,7 +464,7 @@ function ppvm_player($ppub, $path, $video) {
             </div>
             
             <!-- Custom Video Controls -->
-            <div id="custom-controls" class="video-controls">
+            <div id="custom-controls" class="video-controls noscript">
                 <div class="controls-row">
                     <button id="play-pause-btn" class="control-btn" aria-label="Play/Pause">▶</button>
                     <div class="progress-container">