Ver código fonte

Initial custom controls

Billy Barrow 1 mês atrás
pai
commit
a215b29cb2
3 arquivos alterados com 786 adições e 14 exclusões
  1. 479 0
      custom-controls.js
  2. 10 0
      ppvm_player.js
  3. 297 14
      video_player.php

+ 479 - 0
custom-controls.js

@@ -0,0 +1,479 @@
+// @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,
+    container: null,
+    
+    /**
+     * 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();
+    },
+    
+    /**
+     * 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());
+        
+        // Seek slider
+        this.seekSlider.addEventListener('input', () => this.seek());
+        
+        // Progress bar click to seek
+        this.progressBar.addEventListener('click', (e) => this.progressBarSeek(e));
+        
+        // Mute button
+        this.muteBtn.addEventListener('click', () => this.toggleMute());
+        
+        // Volume slider
+        this.volumeSlider.addEventListener('input', () => this.updateVolume());
+        
+        // Fullscreen button
+        this.fullscreenBtn.addEventListener('click', () => this.toggleFullscreen());
+        
+        // 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.video.addEventListener('pause', () => {
+            this.updatePlayPauseButton();
+            this.showControls(); // Show controls when paused
+        });
+        this.video.addEventListener('seeking', () => {
+            this.showControls(); // Show controls when seeking
+        });
+        this.video.addEventListener('seeked', () => {
+            this.hideControlsWithTimeout(); // Auto-hide 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.showControls());
+            this.container.addEventListener('mouseleave', () => this.hideControls());
+            this.container.addEventListener('mouseenter', () => this.showControls());
+        }
+        
+        // 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 after a delay in fullscreen mode if video is playing
+        if (isFullscreen && !this.video.paused) {
+            this.hideControlsWithTimeout();
+        }
+    },
+    
+    /**
+     * 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');
+        }
+        // Only reset timeout if video is playing (to allow auto-hide when idle)
+        if (!this.video.paused) {
+            this.resetControlsTimeout();
+            this.hideControlsWithTimeout();
+        }
+    },
+    
+    /**
+     * Hide the controls
+     */
+    hideControls() {
+        if (!this.video.paused) {
+            this.controls.classList.remove('show');
+            if (this.topControls) {
+                this.topControls.classList.remove('show');
+            }
+        }
+    },
+    
+    /**
+     * 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
+    },
+    
+    /**
+     * 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();
+            }
+        }, 1000);
+    },
+};
+
+// @license-end

+ 10 - 0
ppvm_player.js

@@ -34,6 +34,11 @@ function setup_playback(description) {
     qualitySelected();
 
     doSpeedTest(startRes, filteredOptions);
+    
+    // Initialize custom video controls
+    if (typeof CustomVideoControls !== 'undefined') {
+        CustomVideoControls.init();
+    }
 }
 
 function qualitySelected() {
@@ -111,6 +116,11 @@ function playStateChanged(playing) {
     else {
         controls.classList.add("paused")
     }
+    
+    // Update custom controls if available
+    if (typeof CustomVideoControls !== 'undefined' && CustomVideoControls.video) {
+        CustomVideoControls.updatePlayPauseButton();
+    }
 }
 
 

+ 297 - 14
video_player.php

@@ -20,6 +20,7 @@ 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;
@@ -28,9 +29,16 @@ function ppvm_player($ppub, $path, $video) {
 
             video {
                 display: block;
-                height: auto; 
+                width: 100%;
+                height: 100%;
+                object-fit: contain;
+            }
+            
+            #video-container {
+                position: relative;
                 width: 100vw;
                 height: 100vh;
+                background-color: #000;
             }
 
             .additional-contols {
@@ -46,12 +54,37 @@ function ppvm_player($ppub, $path, $video) {
                 padding-top: 0px;
                 padding-bottom: 0px;
                 font-size: 14px;
+                z-index: 2147483646; /* High z-index to stay visible in fullscreen */
             }
 
             body:hover .additional-contols.javascript, .paused.javascript, .noscript {
                 top: 0px;
                 opacity: 1;
             }
+            
+            .additional-contols.show {
+                top: 0px;
+                opacity: 1;
+            }
+            
+            /* Ensure top controls are visible in fullscreen mode */
+            :fullscreen .additional-contols,
+            :-webkit-full-screen .additional-contols,
+            :-moz-full-screen .additional-contols,
+            :-ms-fullscreen .additional-contols {
+                opacity: 1 !important;
+                top: 0 !important;
+                z-index: 2147483646 !important;
+            }
+            
+            /* Also ensure top controls are visible when hovering in fullscreen */
+            :fullscreen:hover .additional-contols,
+            :-webkit-full-screen:hover .additional-contols,
+            :-moz-full-screen:hover .additional-contols,
+            :-ms-fullscreen:hover .additional-contols {
+                opacity: 1 !important;
+                top: 0 !important;
+            }
 
             .noscript-text {
                 white-space: nowrap;
@@ -185,28 +218,278 @@ function ppvm_player($ppub, $path, $video) {
             }
 
 
+            /* Custom Video Controls Styles */
+            .video-controls {
+                position: absolute;
+                bottom: 0;
+                left: 0;
+                right: 0;
+                background: rgba(45, 45, 45, 0.8);
+                padding: 10px;
+                color: #ffffff;
+                opacity: 0;
+                transition: opacity 0.3s ease;
+                z-index: 10;
+            }
+
+            .video-controls.show {
+                opacity: 1;
+            }
+
+            body:hover .video-controls,
+            .video-controls:hover,
+            .video-controls:focus-within {
+                opacity: 1;
+            }
+            
+            /* Ensure controls are initially visible and properly positioned */
+            #video-container .video-controls {
+                position: absolute !important;
+                bottom: 0 !important;
+                left: 0 !important;
+                right: 0 !important;
+            }
+            
+            /* Ensure controls are properly positioned in fullscreen mode */
+            :fullscreen #video-container .video-controls,
+            :-webkit-full-screen #video-container .video-controls,
+            :-moz-full-screen #video-container .video-controls,
+            :-ms-fullscreen #video-container .video-controls {
+                position: absolute;
+                bottom: 0;
+                left: 0;
+                right: 0;
+                z-index: 2147483647;
+            }
+            
+            /* Ensure top controls are visible in fullscreen mode */
+            :fullscreen .additional-contols,
+            :-webkit-full-screen .additional-contols,
+            :-moz-full-screen .additional-contols,
+            :-ms-fullscreen .additional-contols {
+                opacity: 1;
+                top: 0;
+                z-index: 2147483646;
+            }
+
+            .controls-row {
+                display: flex;
+                align-items: center;
+                gap: 10px;
+            }
+
+            .control-btn {
+                background: none;
+                border: none;
+                color: #ffffff;
+                cursor: pointer;
+                font-size: 18px;
+                padding: 5px;
+                border-radius: 3px;
+                transition: background-color 0.2s;
+            }
+
+            .control-btn:hover {
+                background-color: rgba(255, 255, 255, 0.2);
+            }
+
+            .control-btn:focus {
+                outline: 2px solid skyblue;
+            }
+
+            .progress-container {
+                flex: 1;
+                position: relative;
+                height: 5px;
+                background-color: rgba(255, 255, 255, 0.3);
+                border-radius: 2.5px;
+                cursor: pointer;
+            }
+
+            .progress-bar {
+                position: absolute;
+                width: 100%;
+                height: 100%;
+                background-color: rgba(255, 255, 255, 0.3);
+                border-radius: 2.5px;
+            }
+
+            .progress-filled {
+                position: absolute;
+                width: 0%;
+                height: 100%;
+                background-color: skyblue;
+                border-radius: 2.5px;
+            }
+            
+            .progress-buffered {
+                position: absolute;
+                width: 0%;
+                height: 100%;
+                background-color: rgba(135, 206, 250, 0.3);
+                border-radius: 2.5px;
+            }
+
+            .seek-slider {
+                position: absolute;
+                width: 100%;
+                height: 100%;
+                opacity: 0;
+                cursor: pointer;
+                -webkit-appearance: none;
+                appearance: none;
+                background: transparent;
+            }
+
+            .seek-slider::-webkit-slider-thumb {
+                -webkit-appearance: none;
+                appearance: none;
+                width: 15px;
+                height: 15px;
+                background: skyblue;
+                border-radius: 50%;
+                cursor: pointer;
+                opacity: 0;
+                transition: opacity 0.2s;
+            }
+
+            .seek-slider::-moz-range-thumb {
+                width: 15px;
+                height: 15px;
+                background: skyblue;
+                border-radius: 50%;
+                cursor: pointer;
+                opacity: 0;
+                transition: opacity 0.2s;
+                border: none;
+            }
+
+            .progress-container:hover .seek-slider::-webkit-slider-thumb,
+            .progress-container:hover .seek-slider::-moz-range-thumb {
+                opacity: 1;
+            }
+
+            .time-display {
+                font-size: 14px;
+                min-width: 100px;
+                text-align: center;
+            }
+
+            .volume-container {
+                display: flex;
+                align-items: center;
+            }
+
+            .volume-slider {
+                width: 80px;
+                height: 5px;
+                background: rgba(255, 255, 255, 0.3);
+                border-radius: 2.5px;
+                outline: none;
+                -webkit-appearance: none;
+                appearance: none;
+                cursor: pointer;
+            }
+
+            .volume-slider::-webkit-slider-thumb {
+                -webkit-appearance: none;
+                appearance: none;
+                width: 12px;
+                height: 12px;
+                background: skyblue;
+                border-radius: 50%;
+                cursor: pointer;
+            }
+
+            .volume-slider::-moz-range-thumb {
+                width: 12px;
+                height: 12px;
+                background: skyblue;
+                border-radius: 50%;
+                cursor: pointer;
+                border: none;
+            }
+
+            /* Responsive adjustments */
+            @media (max-width: 768px) {
+                .video-controls {
+                    padding: 8px;
+                }
+                
+                .control-btn {
+                    font-size: 16px;
+                    padding: 3px;
+                }
+                
+                .time-display {
+                    font-size: 12px;
+                    min-width: 80px;
+                }
+                
+                .volume-slider {
+                    width: 60px;
+                }
+            }
+
+            @media (max-width: 480px) {
+                .controls-row {
+                    gap: 5px;
+                }
+                
+                .time-display {
+                    display: none;
+                }
+                
+                .volume-slider {
+                    width: 50px;
+                }
+            }
+
         </style>
     </head>
     <body>
 
-        <video controls id="player" poster="<?php echo($video->metadata["poster"]);?>" preload="metadata" src="<?php echo($video->metadata["master"]);?>">
-        </video>
-
-        <div id="no-script" class="additional-contols noscript">
-            <div class="additional-control site">
-                <a href="<?php echo(SITE_URL);?>/<?php echo($_GET["ppub"]);?>/<?php echo($_GET["asset"]);?>" target="_blank" title="<?php echo(htmlentities($metadata["title"]));?>"><strong><?php echo(htmlentities($short_title));?></strong></a>
-                <span class="noscript-text">For the best experience, please enable JavaScript.</span>
+        <div id="video-container">
+            <video id="player" poster="<?php echo($video->metadata["poster"]);?>" preload="metadata" src="<?php echo($video->metadata["master"]);?>">
+            </video>
+            
+            <!-- Top Controls (moved inside video container) -->
+            <div id="controls" class="additional-contols paused">
+                <div class="additional-control site">
+                    <a href="<?php echo(SITE_URL);?>/<?php echo($_GET["ppub"]);?>/<?php echo($_GET["asset"]);?>" target="_blank" title="<?php echo(htmlentities($metadata["title"]));?>"><strong><?php echo(htmlentities($short_title));?></strong></a>
+                    <button onclick="showInfo()">Info</button>
+                    <button onclick="shareVideo()">Share</button>
+                    <button onclick="downloadVideo()">Download</button>
+                    <select name="quality" id="quality-selector" onchange="qualitySelected()">
+                    </select>
+                </div>
+            </div>
+            
+            <!-- Custom Video Controls -->
+            <div id="custom-controls" class="video-controls">
+                <div class="controls-row">
+                    <button id="play-pause-btn" class="control-btn" aria-label="Play/Pause">▶</button>
+                    <div class="progress-container">
+                        <div id="progress-bar" class="progress-bar">
+                            <div id="progress-filled" class="progress-filled"></div>
+                            <div id="progress-buffered" class="progress-buffered"></div>
+                        </div>
+                        <input type="range" id="seek-slider" class="seek-slider" min="0" max="100" value="0" step="0.1">
+                    </div>
+                    <span id="time-display" class="time-display">0:00 / 0:00</span>
+                    <button id="mute-btn" class="control-btn" aria-label="Mute/Unmute">🔊</button>
+                    <div class="volume-container">
+                        <input type="range" id="volume-slider" class="volume-slider" min="0" max="1" value="1" step="0.1">
+                    </div>
+                    <button id="fullscreen-btn" class="control-btn" aria-label="Fullscreen">⛶</button>
+                </div>
             </div>
         </div>
 
-        <div id="controls" class="additional-contols paused">
+        <div id="no-script" class="additional-contols noscript">
             <div class="additional-control site">
                 <a href="<?php echo(SITE_URL);?>/<?php echo($_GET["ppub"]);?>/<?php echo($_GET["asset"]);?>" target="_blank" title="<?php echo(htmlentities($metadata["title"]));?>"><strong><?php echo(htmlentities($short_title));?></strong></a>
-                <button onclick="showInfo()">Info</button>
-                <button onclick="shareVideo()">Share</button>
-                <button onclick="downloadVideo()">Download</button>
-                <select name="quality" id="quality-selector" onchange="qualitySelected()">  
-                </select>
+                <span class="noscript-text">For the best experience, please enable JavaScript.</span>
             </div>
         </div>