Last active
August 25, 2025 07:37
-
-
Save notsopreety/1d84f56dcb71c42281405b4bac8e84d5 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>VIDEO_TITLE</title> | |
| <style> | |
| :root { | |
| --primary-color: #3498db; | |
| --controls-bg: rgba(0, 0, 0, 0.6); | |
| --text-color: #ffffff; | |
| --icon-size: 24px; | |
| --font-size: 14px; | |
| } | |
| body, html { | |
| margin: 0; | |
| padding: 0; | |
| width: 100%; | |
| height: 100%; | |
| overflow: hidden; | |
| background-color: #000; | |
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; | |
| color: var(--text-color); | |
| } | |
| .player-container { | |
| position: relative; | |
| width: 100%; | |
| height: 100%; | |
| background-color: #000; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| } | |
| video { | |
| width: 100%; | |
| height: 100%; | |
| display: block; | |
| } | |
| /* Loading Spinner */ | |
| .loading-spinner { | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| width: 50px; | |
| height: 50px; | |
| border: 4px solid rgba(255, 255, 255, 0.3); | |
| border-top-color: var(--text-color); | |
| border-radius: 50%; | |
| animation: spin 1s linear infinite; | |
| z-index: 10; | |
| display: none; /* Hidden by default */ | |
| } | |
| .player-container.loading .loading-spinner { | |
| display: block; | |
| } | |
| @keyframes spin { | |
| to { transform: rotate(360deg); } | |
| } | |
| .controls-overlay { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| bottom: 0; | |
| opacity: 0; | |
| transition: opacity 0.3s ease-in-out; | |
| cursor: pointer; | |
| z-index: 5; | |
| } | |
| .player-container:hover .controls-overlay, | |
| .player-container.paused .controls-overlay, | |
| .player-container.controls-visible .controls-overlay { | |
| opacity: 1; | |
| } | |
| .center-controls { | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| display: none; | |
| } | |
| .player-container.paused .center-controls { | |
| display: block; | |
| } | |
| .big-play-pause-btn { | |
| background: var(--controls-bg); | |
| border: none; | |
| border-radius: 50%; | |
| width: 70px; | |
| height: 70px; | |
| cursor: pointer; | |
| padding-left: 5px; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| } | |
| .big-play-pause-btn svg { | |
| width: 40px; | |
| height: 40px; | |
| fill: var(--text-color); | |
| } | |
| .bottom-controls { | |
| position: absolute; | |
| bottom: 0; | |
| left: 0; | |
| right: 0; | |
| padding: 10px; | |
| background: linear-gradient(to top, rgba(0, 0, 0, 0.8), transparent); | |
| display: flex; | |
| flex-direction: column; | |
| gap: 5px; | |
| } | |
| .progress-bar-container { | |
| width: 100%; | |
| height: 15px; | |
| cursor: pointer; | |
| position: relative; | |
| display: flex; | |
| align-items: center; | |
| } | |
| .progress-bar-background, .buffered-bar, .progress-bar { | |
| position: absolute; | |
| height: 4px; | |
| width: 100%; | |
| border-radius: 2px; | |
| transition: height 0.2s ease; | |
| } | |
| .progress-bar-container:hover .progress-bar-background, | |
| .progress-bar-container:hover .buffered-bar, | |
| .progress-bar-container:hover .progress-bar { | |
| height: 6px; | |
| } | |
| .progress-bar-background { background-color: rgba(255, 255, 255, 0.3); } | |
| .buffered-bar { background-color: rgba(255, 255, 255, 0.5); width: 0; } | |
| .progress-bar { background-color: var(--primary-color); width: 0; } | |
| .progress-thumb { | |
| position: absolute; | |
| width: 14px; | |
| height: 14px; | |
| background-color: var(--primary-color); | |
| border-radius: 50%; | |
| transform: scale(0); | |
| left: 0; | |
| transition: transform 0.2s ease; | |
| } | |
| .progress-bar-container:hover .progress-thumb { | |
| transform: scale(1); | |
| } | |
| .buttons-row { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| } | |
| .controls-left, .controls-right { | |
| display: flex; | |
| align-items: center; | |
| gap: 15px; | |
| } | |
| .icon-btn { | |
| background: none; | |
| border: none; | |
| cursor: pointer; | |
| padding: 5px; | |
| display: flex; | |
| align-items: center; | |
| color: var(--text-color); | |
| } | |
| .icon-btn svg { | |
| width: var(--icon-size); | |
| height: var(--icon-size); | |
| fill: var(--text-color); | |
| } | |
| .time-display { | |
| font-size: var(--font-size); | |
| white-space: nowrap; | |
| } | |
| .volume-controls { | |
| display: flex; | |
| align-items: center; | |
| } | |
| .volume-slider { | |
| width: 0; | |
| opacity: 0; | |
| transition: width 0.3s ease, opacity 0.3s ease; | |
| cursor: pointer; | |
| accent-color: var(--primary-color); | |
| } | |
| .volume-controls:hover .volume-slider { | |
| width: 80px; | |
| opacity: 1; | |
| margin-left: 10px; | |
| } | |
| .playback-speed { | |
| position: relative; | |
| } | |
| .speed-menu { | |
| display: none; | |
| position: absolute; | |
| bottom: 120%; | |
| right: 0; | |
| background: var(--controls-bg); | |
| border-radius: 4px; | |
| padding: 5px 0; | |
| list-style: none; | |
| margin: 0; | |
| min-width: 80px; | |
| } | |
| .speed-menu li { | |
| padding: 8px 15px; | |
| cursor: pointer; | |
| font-size: var(--font-size); | |
| } | |
| .speed-menu li:hover { background-color: rgba(255, 255, 255, 0.2); } | |
| .speed-menu li.active { font-weight: bold; color: var(--primary-color); } | |
| .seek-overlay { | |
| position: absolute; | |
| top: 0; | |
| width: 50%; | |
| height: 100%; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| pointer-events: none; | |
| opacity: 0; | |
| } | |
| .seek-overlay.backward { left: 0; } | |
| .seek-overlay.forward { right: 0; } | |
| .seek-overlay svg { | |
| width: 50px; | |
| height: 50px; | |
| fill: var(--text-color); | |
| background: rgba(0,0,0,0.5); | |
| border-radius: 50%; | |
| padding: 10px; | |
| } | |
| @keyframes seek-animation { | |
| 0% { opacity: 1; transform: scale(1); } | |
| 100% { opacity: 0; transform: scale(1.3); } | |
| } | |
| .seek-overlay.active { animation: seek-animation 0.5s ease-out forwards; } | |
| /* --- Responsive Design --- */ | |
| /* Tablet */ | |
| @media (max-width: 768px) { | |
| .controls-left, .controls-right { gap: 10px; } | |
| .volume-controls:hover .volume-slider { width: 70px; } | |
| } | |
| /* Mobile */ | |
| @media (max-width: 600px) { | |
| :root { | |
| --icon-size: 20px; | |
| --font-size: 12px; | |
| } | |
| .bottom-controls { padding: 8px; } | |
| .controls-left, .controls-right { gap: 5px; } | |
| .volume-controls:hover .volume-slider { width: 60px; } | |
| .big-play-pause-btn { width: 60px; height: 60px; } | |
| .big-play-pause-btn svg { width: 30px; height: 30px; } | |
| /* Mobile-specific adjustments */ | |
| .progress-bar-container { height: 12px; } | |
| .progress-bar-background, .buffered-bar, .progress-bar { height: 3px; } | |
| .progress-bar-container:hover .progress-bar-background, | |
| .progress-bar-container:hover .buffered-bar, | |
| .progress-bar-container:hover .progress-bar { height: 5px; } | |
| .seek-overlay svg { | |
| width: 40px; | |
| height: 40px; | |
| padding: 8px; | |
| } | |
| } | |
| /* Small Mobile */ | |
| @media (max-width: 480px) { | |
| .time-display { | |
| display: none; /* Hide time display on very small screens to prevent clutter */ | |
| } | |
| .volume-slider { | |
| display: none; /* Hide slider and only keep mute toggle on smallest screens */ | |
| } | |
| .volume-controls:hover .volume-slider { | |
| display: none; | |
| } | |
| /* Even smaller adjustments for very small screens */ | |
| .bottom-controls { padding: 6px; } | |
| .controls-left, .controls-right { gap: 3px; } | |
| .big-play-pause-btn { width: 50px; height: 50px; } | |
| .big-play-pause-btn svg { width: 25px; height: 25px; } | |
| .speed-menu { | |
| bottom: 110%; | |
| min-width: 70px; | |
| } | |
| .speed-menu li { | |
| padding: 6px 12px; | |
| font-size: 11px; | |
| } | |
| } | |
| /* Portrait orientation adjustments for mobile */ | |
| @media (max-width: 600px) and (orientation: portrait) { | |
| .player-container { | |
| /* Ensure proper aspect ratio handling in portrait */ | |
| aspect-ratio: 16/9; | |
| max-height: 100vh; | |
| max-width: 100vw; | |
| } | |
| video { | |
| object-fit: contain; | |
| } | |
| } | |
| /* Landscape orientation adjustments for mobile */ | |
| @media (max-width: 600px) and (orientation: landscape) { | |
| .player-container { | |
| /* Optimize for landscape viewing */ | |
| aspect-ratio: 16/9; | |
| max-height: 100vh; | |
| max-width: 100vw; | |
| } | |
| video { | |
| object-fit: contain; | |
| } | |
| /* Adjust controls for landscape */ | |
| .bottom-controls { | |
| padding: 6px 10px; | |
| } | |
| .big-play-pause-btn { | |
| width: 50px; | |
| height: 50px; | |
| } | |
| } | |
| /* Fullscreen mobile optimizations */ | |
| .player-container:fullscreen { | |
| background-color: #000; | |
| } | |
| .player-container:fullscreen video { | |
| object-fit: contain; | |
| max-height: 100vh; | |
| max-width: 100vw; | |
| } | |
| /* Webkit fullscreen support */ | |
| .player-container:-webkit-full-screen { | |
| background-color: #000; | |
| } | |
| .player-container:-webkit-full-screen video { | |
| object-fit: contain; | |
| max-height: 100vh; | |
| max-width: 100vw; | |
| } | |
| /* Mozilla fullscreen support */ | |
| .player-container:-moz-full-screen { | |
| background-color: #000; | |
| } | |
| .player-container:-moz-full-screen video { | |
| object-fit: contain; | |
| max-height: 100vh; | |
| max-width: 100vw; | |
| } | |
| /* Orientation-specific styles */ | |
| .player-container.landscape .bottom-controls { | |
| padding: 8px 12px; | |
| } | |
| .player-container.portrait .big-play-pause-btn { | |
| width: 70px; | |
| height: 70px; | |
| } | |
| .player-container.portrait .big-play-pause-btn svg { | |
| width: 35px; | |
| height: 35px; | |
| } | |
| /* Touch-friendly improvements for mobile */ | |
| @media (max-width: 600px) { | |
| .icon-btn { | |
| min-width: 44px; | |
| min-height: 44px; | |
| padding: 8px; | |
| } | |
| .progress-bar-container { | |
| min-height: 44px; | |
| padding: 20px 0; | |
| } | |
| /* Improve touch targets */ | |
| .volume-controls:hover .volume-slider { | |
| width: 80px; | |
| height: 20px; | |
| } | |
| /* Better spacing for touch */ | |
| .controls-left, .controls-right { | |
| gap: 8px; | |
| } | |
| } | |
| /* Prevent text selection on mobile */ | |
| .player-container { | |
| -webkit-user-select: none; | |
| -moz-user-select: none; | |
| -ms-user-select: none; | |
| user-select: none; | |
| -webkit-touch-callout: none; | |
| -webkit-tap-highlight-color: transparent; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="player-container" id="player-container"> | |
| <video | |
| id="main-video" | |
| src="VIDEO_URL" | |
| preload="metadata"> | |
| </video> | |
| <div class="loading-spinner" id="loading-spinner"></div> | |
| <div class="controls-overlay" id="controls-overlay"> | |
| <div class="seek-overlay backward" id="seek-backward-overlay"> | |
| <svg viewBox="0 0 24 24"><path d="M11.67 3.87L9.9 2.1 0 12l9.9 9.9 1.77-1.77L3.54 12z"></path><path d="M21.57 3.87L19.8 2.1 9.9 12l9.9 9.9 1.77-1.77L13.44 12z"></path></svg> | |
| </div> | |
| <div class="seek-overlay forward" id="seek-forward-overlay"> | |
| <svg viewBox="0 0 24 24"><path d="M12.33 3.87L14.1 2.1 24 12l-9.9 9.9-1.77-1.77L20.46 12z"></path><path d="M2.43 3.87L4.2 2.1l9.9 9.9-9.9 9.9-1.77-1.77L8.56 12z"></path></svg> | |
| </div> | |
| <div class="center-controls"> | |
| <button class="icon-btn big-play-pause-btn" id="center-play-pause-btn"> | |
| <svg class="play-icon" viewBox="0 0 24 24"><path d="M16.6582 9.28638C18.098 10.1862 18.8178 10.6361 19.0647 11.2122C19.2803 11.7152 19.2803 12.2847 19.0647 12.7878C18.8178 13.3638 18.098 13.8137 16.6582 14.7136L9.896 18.94C8.29805 19.9387 7.49907 20.4381 6.83973 20.385C6.26501 20.3388 5.73818 20.0469 5.3944 19.584C5 19.053 5 18.1108 5 16.2264V7.77357C5 5.88919 5 4.94701 5.3944 4.41598C5.73818 3.9531 6.26501 3.66111 6.83973 3.6149C7.49907 3.5619 8.29805 4.06126 9.896 5.05998L16.6582 9.28638Z" stroke="#FFFFFF" stroke-width="2" stroke-linejoin="round"/></svg> | |
| <svg class="pause-icon" viewBox="0 0 24 24" style="display:none;"><path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"></path></svg> | |
| </button> | |
| </div> | |
| <div class="bottom-controls"> | |
| <div class="progress-bar-container" id="progress-bar-container"> | |
| <div class="progress-bar-background"></div> | |
| <div class="buffered-bar" id="buffered-bar"></div> | |
| <div class="progress-bar" id="progress-bar"></div> | |
| <div class="progress-thumb" id="progress-thumb"></div> | |
| </div> | |
| <div class="buttons-row"> | |
| <div class="controls-left"> | |
| <button class="icon-btn" id="play-pause-btn"> | |
| <svg class="play-icon" viewBox="0 0 24 24"><path d="M8 5v14l11-7z"></path></svg> | |
| <svg class="pause-icon" viewBox="0 0 24 24" style="display:none;"><path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"></path></svg> | |
| </button> | |
| <button class="icon-btn" id="rewind-btn" title="Rewind 10s (j)"> | |
| <svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> | |
| <path d="M13 8.76844L19.0966 4.30838C20.3991 3.41122 21.9998 4.57895 21.9998 6.42632L21.9998 17.5737C21.9998 19.4211 20.3991 20.5888 19.0966 19.6916L13 15.2316" stroke="#FFFFFF" stroke-width="1.5"/> | |
| <path d="M2.92136 10.1468C1.69288 10.9545 1.69288 13.0455 2.92135 13.8532L10.3388 18.7302C11.5327 19.5152 13 18.4934 13 16.877L13 7.12303C13 5.50658 11.5327 4.48482 10.3388 5.26983L2.92136 10.1468Z" stroke="#FFFFFF" stroke-width="1.5"/> | |
| </svg> | |
| </button> | |
| <button class="icon-btn" id="forward-btn" title="Forward 10s (l)"> | |
| <svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> | |
| <path d="M10.9998 8.76844L4.90312 4.30838C3.60064 3.41122 2 4.57895 2 6.42632L2 17.5737C2 19.4211 3.60065 20.5888 4.90313 19.6916L10.9998 15.2316" stroke="#FFFFFF" stroke-width="1.5"/> | |
| <path d="M21.0786 10.1468C22.3071 10.9545 22.3071 13.0455 21.0786 13.8532L13.6612 18.7302C12.4673 19.5152 11 18.4934 11 16.877L11 7.12303C11 5.50658 12.4673 4.48482 13.6612 5.26983L21.0786 10.1468Z" stroke="#FFFFFF" stroke-width="1.5"/> | |
| </svg> | |
| </button> | |
| <div class="volume-controls"> | |
| <button class="icon-btn" id="mute-btn"> | |
| <svg class="volume-high-icon" viewBox="0 0 24 24"><path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"></path></svg> | |
| <svg class="volume-low-icon" viewBox="0 0 24 24" style="display:none;"><path d="M18.5 12c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM5 9v6h4l5 5V4L9 9H5z"></path></svg> | |
| <svg class="volume-muted-icon" viewBox="0 0 24 24" style="display:none;"><path d="M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z"></path></svg> | |
| </button> | |
| <input type="range" class="volume-slider" id="volume-slider" min="0" max="1" step="0.01" value="1"> | |
| </div> | |
| <div class="time-display" id="time-display">00:00 / 00:00</div> | |
| </div> | |
| <div class="controls-right"> | |
| <div class="playback-speed"> | |
| <button class="icon-btn" id="speed-btn"> | |
| <span style="font-size: var(--font-size); font-weight: bold;">1x</span> | |
| </button> | |
| <ul class="speed-menu" id="speed-menu"> | |
| <li data-speed="2">2x</li><li data-speed="1.5">1.5x</li><li data-speed="1.25">1.25x</li><li data-speed="1" class="active">1x</li><li data-speed="0.75">0.75x</li><li data-speed="0.5">0.5x</li> | |
| </ul> | |
| </div> | |
| <button class="icon-btn" id="fullscreen-btn"> | |
| <svg class="fullscreen-open-icon" viewBox="0 0 24 24"><path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"></path></svg> | |
| <svg class="fullscreen-close-icon" viewBox="0 0 24 24" style="display:none;"><path d="M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z"></path></svg> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', () => { | |
| const playerContainer = document.getElementById('player-container'); | |
| const video = document.getElementById('main-video'); | |
| const controlsOverlay = document.getElementById('controls-overlay'); | |
| const playPauseBtn = document.getElementById('play-pause-btn'); | |
| const centerPlayPauseBtn = document.getElementById('center-play-pause-btn'); | |
| const playIcons = document.querySelectorAll('.play-icon'); | |
| const pauseIcons = document.querySelectorAll('.pause-icon'); | |
| const rewindBtn = document.getElementById('rewind-btn'); | |
| const forwardBtn = document.getElementById('forward-btn'); | |
| const progressBarContainer = document.getElementById('progress-bar-container'); | |
| const progressBar = document.getElementById('progress-bar'); | |
| const bufferedBar = document.getElementById('buffered-bar'); | |
| const progressThumb = document.getElementById('progress-thumb'); | |
| const timeDisplay = document.getElementById('time-display'); | |
| const muteBtn = document.getElementById('mute-btn'); | |
| const volumeHighIcon = document.querySelector('.volume-high-icon'); | |
| const volumeLowIcon = document.querySelector('.volume-low-icon'); | |
| const volumeMutedIcon = document.querySelector('.volume-muted-icon'); | |
| const volumeSlider = document.getElementById('volume-slider'); | |
| const speedBtn = document.getElementById('speed-btn'); | |
| const speedMenu = document.getElementById('speed-menu'); | |
| const fullscreenBtn = document.getElementById('fullscreen-btn'); | |
| const fullscreenOpenIcon = document.querySelector('.fullscreen-open-icon'); | |
| const fullscreenCloseIcon = document.querySelector('.fullscreen-close-icon'); | |
| const seekBackwardOverlay = document.getElementById('seek-backward-overlay'); | |
| const seekForwardOverlay = document.getElementById('seek-forward-overlay'); | |
| let controlsTimeout; | |
| let lastClickTime = 0; | |
| const formatTime = (time) => { | |
| const sec = Math.floor(time % 60); | |
| const min = Math.floor(time / 60) % 60; | |
| const hr = Math.floor(time / 3600); | |
| return hr > 0 ? `${String(hr).padStart(2, '0')}:${String(min).padStart(2, '0')}:${String(sec).padStart(2, '0')}` : `${String(min).padStart(2, '0')}:${String(sec).padStart(2, '0')}`; | |
| }; | |
| const updateIconSet = (iconsToShow, iconsToHide) => { | |
| iconsToShow.forEach(i => i.style.display = 'block'); | |
| iconsToHide.forEach(i => i.style.display = 'none'); | |
| }; | |
| const togglePlayPause = () => video.paused ? video.play() : video.pause(); | |
| const updatePlayPauseIcons = () => { | |
| if (video.paused) { | |
| updateIconSet(playIcons, pauseIcons); | |
| playerContainer.classList.add('paused'); | |
| playerContainer.classList.remove('playing'); | |
| } else { | |
| updateIconSet(pauseIcons, playIcons); | |
| playerContainer.classList.add('playing'); | |
| playerContainer.classList.remove('paused'); | |
| } | |
| }; | |
| const updateProgress = () => { | |
| const progressPercent = (video.currentTime / video.duration) * 100; | |
| progressBar.style.width = `${progressPercent}%`; | |
| progressThumb.style.left = `calc(${progressPercent}% - 7px)`; | |
| if (!isNaN(video.duration)) { | |
| timeDisplay.textContent = `${formatTime(video.currentTime)} / ${formatTime(video.duration)}`; | |
| } | |
| }; | |
| const updateBuffer = () => { | |
| if (video.buffered.length > 0) { | |
| bufferedBar.style.width = `${(video.buffered.end(video.buffered.length - 1) / video.duration) * 100}%`; | |
| } | |
| }; | |
| const seek = (e) => video.currentTime = ((e.clientX - progressBarContainer.getBoundingClientRect().left) / progressBarContainer.clientWidth) * video.duration; | |
| const toggleMute = () => video.muted = !video.muted; | |
| const updateVolumeUI = () => { | |
| if (video.muted || video.volume === 0) { | |
| updateIconSet([volumeMutedIcon], [volumeHighIcon, volumeLowIcon]); | |
| volumeSlider.value = 0; | |
| } else if (video.volume > 0.5) { | |
| updateIconSet([volumeHighIcon], [volumeMutedIcon, volumeLowIcon]); | |
| volumeSlider.value = video.volume; | |
| } else { | |
| updateIconSet([volumeLowIcon], [volumeMutedIcon, volumeHighIcon]); | |
| volumeSlider.value = video.volume; | |
| } | |
| }; | |
| const toggleSpeedMenu = () => speedMenu.style.display = speedMenu.style.display === 'block' ? 'none' : 'block'; | |
| const setPlaybackSpeed = (speed) => { | |
| video.playbackRate = speed; | |
| speedBtn.querySelector('span').textContent = `${speed}x`; | |
| speedMenu.querySelectorAll('li').forEach(item => item.classList.toggle('active', parseFloat(item.dataset.speed) === speed)); | |
| toggleSpeedMenu(); | |
| }; | |
| const toggleFullscreen = () => { | |
| if (!document.fullscreenElement) { | |
| // Request fullscreen | |
| if (playerContainer.requestFullscreen) { | |
| playerContainer.requestFullscreen().catch(err => console.error(err)); | |
| } else if (playerContainer.webkitRequestFullscreen) { | |
| playerContainer.webkitRequestFullscreen().catch(err => console.error(err)); | |
| } else if (playerContainer.mozRequestFullScreen) { | |
| playerContainer.mozRequestFullScreen().catch(err => console.error(err)); | |
| } else if (playerContainer.msRequestFullscreen) { | |
| playerContainer.msRequestFullscreen().catch(err => console.error(err)); | |
| } | |
| // For mobile devices, try to lock to landscape orientation | |
| if (screen.orientation && screen.orientation.lock) { | |
| screen.orientation.lock('landscape').catch(err => { | |
| console.log('Could not lock orientation:', err); | |
| }); | |
| } | |
| } else { | |
| // Exit fullscreen | |
| if (document.exitFullscreen) { | |
| document.exitFullscreen(); | |
| } else if (document.webkitExitFullscreen) { | |
| document.webkitExitFullscreen(); | |
| } else if (document.mozCancelFullScreen) { | |
| document.mozCancelFullScreen(); | |
| } else if (document.msExitFullscreen) { | |
| document.msExitFullscreen(); | |
| } | |
| // Unlock orientation when exiting fullscreen | |
| if (screen.orientation && screen.orientation.unlock) { | |
| screen.orientation.unlock(); | |
| } | |
| } | |
| }; | |
| const updateFullscreenIcons = () => { | |
| const isFullscreen = document.fullscreenElement || | |
| document.webkitFullscreenElement || | |
| document.mozFullScreenElement || | |
| document.msFullscreenElement; | |
| updateIconSet(isFullscreen ? [fullscreenCloseIcon] : [fullscreenOpenIcon], | |
| isFullscreen ? [fullscreenOpenIcon] : [fullscreenCloseIcon]); | |
| }; | |
| // Handle orientation changes | |
| const handleOrientationChange = () => { | |
| // Adjust player layout based on orientation | |
| if (window.innerWidth < 600) { | |
| if (window.orientation === 90 || window.orientation === -90) { | |
| // Landscape | |
| playerContainer.classList.add('landscape'); | |
| playerContainer.classList.remove('portrait'); | |
| } else { | |
| // Portrait | |
| playerContainer.classList.add('portrait'); | |
| playerContainer.classList.remove('landscape'); | |
| } | |
| } | |
| }; | |
| // Initialize orientation classes | |
| const initializeOrientation = () => { | |
| handleOrientationChange(); | |
| // Set initial orientation class | |
| if (window.innerWidth < 600) { | |
| if (window.orientation === 90 || window.orientation === -90) { | |
| playerContainer.classList.add('landscape'); | |
| } else { | |
| playerContainer.classList.add('portrait'); | |
| } | |
| } | |
| }; | |
| const triggerSeekAnimation = (overlay) => { | |
| overlay.classList.remove('active'); | |
| void overlay.offsetWidth; | |
| overlay.classList.add('active'); | |
| }; | |
| const handleVideoClick = (e) => { | |
| const currentTime = new Date().getTime(); | |
| if (currentTime - lastClickTime < 300) { | |
| const rect = video.getBoundingClientRect(); | |
| const clickX = e.clientX - rect.left; | |
| if (clickX < rect.width / 3) { | |
| video.currentTime -= 10; | |
| triggerSeekAnimation(seekBackwardOverlay); | |
| } else if (clickX > (rect.width * 2) / 3) { | |
| video.currentTime += 10; | |
| triggerSeekAnimation(seekForwardOverlay); | |
| } else togglePlayPause(); | |
| } else togglePlayPause(); | |
| lastClickTime = currentTime; | |
| }; | |
| const showControls = () => { | |
| playerContainer.classList.add('controls-visible'); | |
| clearTimeout(controlsTimeout); | |
| }; | |
| const hideControls = () => { | |
| if (video.paused) return; | |
| controlsTimeout = setTimeout(() => { | |
| playerContainer.classList.remove('controls-visible'); | |
| if (speedMenu.style.display === 'block') toggleSpeedMenu(); | |
| }, 3000); | |
| }; | |
| const handleKeyPress = (e) => { | |
| if (e.target.matches('input, textarea, [contenteditable]')) return; | |
| e.preventDefault(); | |
| switch(e.key.toLowerCase()) { | |
| case ' ': case 'k': togglePlayPause(); break; | |
| case 'm': toggleMute(); break; | |
| case 'f': toggleFullscreen(); break; | |
| case 'j': video.currentTime -= 10; break; | |
| case 'l': video.currentTime += 10; break; | |
| case 'arrowleft': video.currentTime -= 5; break; | |
| case 'arrowright': video.currentTime += 5; break; | |
| case 'arrowup': video.volume = Math.min(1, video.volume + 0.1); break; | |
| case 'arrowdown': video.volume = Math.max(0, video.volume - 0.1); break; | |
| } | |
| }; | |
| video.addEventListener('play', updatePlayPauseIcons); | |
| video.addEventListener('pause', updatePlayPauseIcons); | |
| video.addEventListener('timeupdate', updateProgress); | |
| video.addEventListener('progress', updateBuffer); | |
| video.addEventListener('volumechange', updateVolumeUI); | |
| video.addEventListener('loadedmetadata', updateProgress); | |
| video.addEventListener('waiting', () => playerContainer.classList.add('loading')); | |
| video.addEventListener('playing', () => playerContainer.classList.remove('loading')); | |
| controlsOverlay.addEventListener('click', (e) => e.target === controlsOverlay && handleVideoClick(e)); | |
| playPauseBtn.addEventListener('click', togglePlayPause); | |
| centerPlayPauseBtn.addEventListener('click', togglePlayPause); | |
| rewindBtn.addEventListener('click', () => video.currentTime -= 10); | |
| forwardBtn.addEventListener('click', () => video.currentTime += 10); | |
| progressBarContainer.addEventListener('click', seek); | |
| muteBtn.addEventListener('click', toggleMute); | |
| volumeSlider.addEventListener('input', (e) => { video.volume = e.target.value; video.muted = video.volume === 0; }); | |
| speedBtn.addEventListener('click', toggleSpeedMenu); | |
| speedMenu.querySelectorAll('li').forEach(item => item.addEventListener('click', () => setPlaybackSpeed(parseFloat(item.dataset.speed)))); | |
| fullscreenBtn.addEventListener('click', toggleFullscreen); | |
| document.addEventListener('fullscreenchange', updateFullscreenIcons); | |
| document.addEventListener('webkitfullscreenchange', updateFullscreenIcons); | |
| document.addEventListener('mozfullscreenchange', updateFullscreenIcons); | |
| document.addEventListener('MSFullscreenChange', updateFullscreenIcons); | |
| // Handle orientation and resize changes | |
| window.addEventListener('orientationchange', handleOrientationChange); | |
| window.addEventListener('resize', handleOrientationChange); | |
| playerContainer.addEventListener('mousemove', () => { showControls(); hideControls(); }); | |
| playerContainer.addEventListener('mouseleave', () => !video.paused && playerContainer.classList.remove('controls-visible')); | |
| playerContainer.addEventListener('mouseenter', showControls); | |
| document.addEventListener('keydown', handleKeyPress); | |
| updatePlayPauseIcons(); | |
| updateVolumeUI(); | |
| hideControls(); | |
| initializeOrientation(); | |
| }); | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment