| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634 |
- // @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
|