Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> | |
| <title>Modern Mobile H5 Video Player</title> | |
| <!-- FontAwesome for Icons --> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| :root { | |
| --primary-color: #3b82f6; | |
| --primary-glow: rgba(59, 130, 246, 0.5); | |
| --bg-dark: #0f172a; | |
| --bg-panel: rgba(30, 41, 59, 0.8); | |
| --text-main: #f8fafc; | |
| --text-muted: #94a3b8; | |
| --control-height: 60px; | |
| --radius: 12px; | |
| } | |
| * { | |
| box-sizing: border-box; | |
| margin: 0; | |
| padding: 0; | |
| -webkit-tap-highlight-color: transparent; | |
| } | |
| body { | |
| font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; | |
| background-color: var(--bg-dark); | |
| color: var(--text-main); | |
| height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| overflow: hidden; /* Prevent scrolling on mobile */ | |
| } | |
| /* --- Header --- */ | |
| header { | |
| padding: 15px 20px; | |
| background: rgba(15, 23, 42, 0.9); | |
| backdrop-filter: blur(10px); | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| border-bottom: 1px solid rgba(255, 255, 255, 0.1); | |
| z-index: 100; | |
| } | |
| .brand { | |
| font-weight: 700; | |
| font-size: 1.1rem; | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| } | |
| .brand i { | |
| color: var(--primary-color); | |
| } | |
| .built-with { | |
| font-size: 0.8rem; | |
| color: var(--text-muted); | |
| text-decoration: none; | |
| transition: color 0.3s ease; | |
| } | |
| .built-with:hover { | |
| color: var(--primary-color); | |
| } | |
| /* --- Main Video Area --- */ | |
| main { | |
| flex: 1; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| padding: 20px; | |
| position: relative; | |
| background: radial-gradient(circle at center, #1e293b 0%, #0f172a 100%); | |
| } | |
| .player-container { | |
| width: 100%; | |
| max-width: 800px; | |
| aspect-ratio: 16 / 9; | |
| background: #000; | |
| border-radius: var(--radius); | |
| box-shadow: 0 20px 50px -12px rgba(0, 0, 0, 0.7); | |
| position: relative; | |
| overflow: hidden; | |
| border: 1px solid rgba(255, 255, 255, 0.05); | |
| } | |
| video { | |
| width: 100%; | |
| height: 100%; | |
| display: block; | |
| object-fit: contain; /* Ensures video fits without cropping */ | |
| } | |
| /* --- Controls Overlay --- */ | |
| .controls-overlay { | |
| position: absolute; | |
| bottom: 0; | |
| left: 0; | |
| width: 100%; | |
| padding: 20px; | |
| background: linear-gradient(to top, rgba(0,0,0,0.9), transparent); | |
| opacity: 0; | |
| transition: opacity 0.3s ease; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 10px; | |
| } | |
| /* Show controls on hover or when playing */ | |
| .player-container:hover .controls-overlay, | |
| .controls-overlay.active { | |
| opacity: 1; | |
| } | |
| /* --- Progress Bar --- */ | |
| .progress-container { | |
| width: 100%; | |
| height: 5px; | |
| background: rgba(255, 255, 255, 0.2); | |
| border-radius: 5px; | |
| cursor: pointer; | |
| position: relative; | |
| transition: height 0.2s; | |
| } | |
| .progress-container:hover { | |
| height: 8px; | |
| } | |
| .progress-fill { | |
| height: 100%; | |
| background: var(--primary-color); | |
| border-radius: 5px; | |
| width: 0%; | |
| position: relative; | |
| box-shadow: 0 0 10px var(--primary-glow); | |
| } | |
| .progress-thumb { | |
| width: 14px; | |
| height: 14px; | |
| background: #fff; | |
| border-radius: 50%; | |
| position: absolute; | |
| right: -7px; | |
| top: 50%; | |
| transform: translateY(-50%) scale(0); | |
| transition: transform 0.2s; | |
| } | |
| .progress-container:hover .progress-thumb { | |
| transform: translateY(-50%) scale(1); | |
| } | |
| /* --- Control Buttons Row --- */ | |
| .controls-row { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| } | |
| .left-controls, .right-controls { | |
| display: flex; | |
| align-items: center; | |
| gap: 15px; | |
| } | |
| .btn-control { | |
| background: none; | |
| border: none; | |
| color: var(--text-main); | |
| font-size: 1.2rem; | |
| cursor: pointer; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| width: 36px; | |
| height: 36px; | |
| border-radius: 50%; | |
| transition: background 0.2s, transform 0.1s; | |
| } | |
| .btn-control:hover { | |
| background: rgba(255, 255, 255, 0.1); | |
| } | |
| .btn-control:active { | |
| transform: scale(0.95); | |
| } | |
| .btn-play { | |
| width: 44px; | |
| height: 44px; | |
| background: rgba(255, 255, 255, 0.15); | |
| backdrop-filter: blur(5px); | |
| } | |
| .btn-play:hover { | |
| background: var(--primary-color); | |
| box-shadow: 0 0 15px var(--primary-glow); | |
| } | |
| /* --- Volume Slider --- */ | |
| .volume-container { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| width: 100px; | |
| } | |
| .volume-slider { | |
| -webkit-appearance: none; | |
| width: 100%; | |
| height: 4px; | |
| background: rgba(255, 255, 255, 0.2); | |
| border-radius: 2px; | |
| outline: none; | |
| } | |
| .volume-slider::-webkit-slider-thumb { | |
| -webkit-appearance: none; | |
| width: 12px; | |
| height: 12px; | |
| background: #fff; | |
| border-radius: 50%; | |
| cursor: pointer; | |
| display: none; /* Hidden unless hovered */ | |
| } | |
| .volume-container:hover .volume-slider::-webkit-slider-thumb { | |
| display: block; | |
| } | |
| /* --- Time Display --- */ | |
| .time-display { | |
| font-size: 0.85rem; | |
| color: var(--text-muted); | |
| font-variant-numeric: tabular-nums; | |
| min-width: 100px; | |
| text-align: right; | |
| } | |
| /* --- Upload Section --- */ | |
| .upload-section { | |
| padding: 10px 20px 20px; | |
| text-align: center; | |
| } | |
| .file-input-wrapper { | |
| position: relative; | |
| overflow: hidden; | |
| display: inline-block; | |
| } | |
| .file-input-btn { | |
| border: 1px dashed var(--text-muted); | |
| color: var(--text-muted); | |
| background: transparent; | |
| padding: 10px 20px; | |
| border-radius: 20px; | |
| cursor: pointer; | |
| font-size: 0.9rem; | |
| transition: all 0.3s; | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| } | |
| .file-input-btn:hover { | |
| border-color: var(--primary-color); | |
| color: var(--primary-color); | |
| } | |
| input[type=file] { | |
| font-size: 100px; | |
| position: absolute; | |
| left: 0; | |
| top: 0; | |
| opacity: 0; | |
| cursor: pointer; | |
| } | |
| /* --- Loading Spinner --- */ | |
| .spinner { | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| color: var(--primary-color); | |
| font-size: 3rem; | |
| display: none; | |
| z-index: 5; | |
| } | |
| /* --- Fullscreen --- */ | |
| .player-container:fullscreen { | |
| border-radius: 0; | |
| } | |
| .player-container:fullscreen video { | |
| object-fit: contain; | |
| } | |
| /* Mobile Specific Adjustments */ | |
| @media (max-width: 600px) { | |
| .player-container { | |
| border-radius: 0; | |
| width: 100vw; | |
| height: 100vh; /* Full screen video on mobile */ | |
| max-width: 100%; | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| } | |
| main { | |
| padding: 0; | |
| display: block; | |
| } | |
| header { | |
| position: absolute; | |
| top: 0; | |
| width: 100%; | |
| z-index: 50; | |
| background: rgba(15, 23, 42, 0.5); | |
| } | |
| .controls-overlay { | |
| background: linear-gradient(to top, rgba(0,0,0,0.95), transparent); | |
| padding: 30px 20px 20px; | |
| } | |
| .time-display { | |
| position: absolute; | |
| bottom: 80px; | |
| left: 20px; | |
| right: 20px; | |
| text-align: left; | |
| text-shadow: 0 1px 2px rgba(0,0,0,0.8); | |
| z-index: 10; | |
| } | |
| .volume-container { | |
| width: 60px; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <header> | |
| <div class="brand"> | |
| <i class="fa-solid fa-play-circle"></i> | |
| <span>FlexPlayer</span> | |
| </div> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="built-with">Built with anycoder</a> | |
| </header> | |
| <main> | |
| <div class="player-container" id="playerContainer"> | |
| <!-- Default Sample Video --> | |
| <video id="videoPlayer" poster="https://images.pexels.com/photos/3129671/pexels-photo-3129671.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1"> | |
| <source src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4" type="video/mp4"> | |
| Your browser does not support the video tag. | |
| </video> | |
| <div class="spinner" id="loadingSpinner"> | |
| <i class="fa-solid fa-circle-notch fa-spin"></i> | |
| </div> | |
| <div class="controls-overlay" id="controlsOverlay"> | |
| <!-- Progress --> | |
| <div class="progress-container" id="progressContainer"> | |
| <div class="progress-fill" id="progressFill"> | |
| <div class="progress-thumb"></div> | |
| </div> | |
| </div> | |
| <!-- Controls Row --> | |
| <div class="controls-row"> | |
| <div class="left-controls"> | |
| <button class="btn-control btn-play" id="playPauseBtn" aria-label="Play/Pause"> | |
| <i class="fa-solid fa-play"></i> | |
| </button> | |
| <button class="btn-control" id="muteBtn" aria-label="Mute"> | |
| <i class="fa-solid fa-volume-high"></i> | |
| </button> | |
| <div class="volume-container"> | |
| <input type="range" class="volume-slider" id="volumeSlider" min="0" max="1" step="0.1" value="1"> | |
| </div> | |
| <div class="time-display"> | |
| <span id="currentTime">0:00</span> / <span id="duration">0:00</span> | |
| </div> | |
| </div> | |
| <div class="right-controls"> | |
| <button class="btn-control" id="pipBtn" aria-label="Picture in Picture"> | |
| <i class="fa-solid fa-clone"></i> | |
| </button> | |
| <button class="btn-control" id="fsBtn" aria-label="Fullscreen"> | |
| <i class="fa-solid fa-expand"></i> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </main> | |
| <div class="upload-section"> | |
| <div class="file-input-wrapper"> | |
| <button class="file-input-btn"> | |
| <i class="fa-solid fa-upload"></i> Load Local Video | |
| </button> | |
| <input type="file" id="videoUpload" accept="video/*"> | |
| </div> | |
| </div> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', () => { | |
| const video = document.getElementById('videoPlayer'); | |
| const container = document.getElementById('playerContainer'); | |
| const playPauseBtn = document.getElementById('playPauseBtn'); | |
| const muteBtn = document.getElementById('muteBtn'); | |
| const volumeSlider = document.getElementById('volumeSlider'); | |
| const progressContainer = document.getElementById('progressContainer'); | |
| const progressFill = document.getElementById('progressFill'); | |
| const currentTimeEl = document.getElementById('currentTime'); | |
| const durationEl = document.getElementById('duration'); | |
| const fsBtn = document.getElementById('fsBtn'); | |
| const pipBtn = document.getElementById('pipBtn'); | |
| const uploadInput = document.getElementById('videoUpload'); | |
| const spinner = document.getElementById('loadingSpinner'); | |
| // --- Utility Functions --- | |
| // Format seconds to MM:SS | |
| function formatTime(seconds) { | |
| if (isNaN(seconds)) return "0:00"; | |
| const m = Math.floor(seconds / 60); | |
| const s = Math.floor(seconds % 60); | |
| return `${m}:${s < 10 ? '0' : ''}${s}`; | |
| } | |
| // --- Video Control Logic --- | |
| function togglePlay() { | |
| if (video.paused || video.ended) { | |
| video.play(); | |
| playPauseBtn.innerHTML = '<i class="fa-solid fa-pause"></i>'; | |
| } else { | |
| video.pause(); | |
| playPauseBtn.innerHTML = '<i class="fa-solid fa-play"></i>'; | |
| } | |
| } | |
| function updatePlayButtonIcon() { | |
| if (video.paused) { | |
| playPauseBtn.innerHTML = '<i class="fa-solid fa-play"></i>'; | |
| } else { | |
| playPauseBtn.innerHTML = '<i class="fa-solid fa-pause"></i>'; | |
| } | |
| } | |
| function updateProgress() { | |
| const percent = (video.currentTime / video.duration) * 100; | |
| progressFill.style.width = `${percent}%`; | |
| currentTimeEl.textContent = formatTime(video.currentTime); | |
| } | |
| function setProgress(e) { | |
| const width = progressContainer.clientWidth; | |
| const clickX = e.offsetX; | |
| const duration = video.duration; | |
| video.currentTime = (clickX / width) * duration; | |
| } | |
| function toggleMute() { | |
| video.muted = !video.muted; | |
| if (video.muted) { | |
| muteBtn.innerHTML = '<i class="fa-solid fa-volume-xmark"></i>'; | |
| volumeSlider.value = 0; | |
| } else { | |
| muteBtn.innerHTML = '<i class="fa-solid fa-volume-high"></i>'; | |
| volumeSlider.value = video.volume; | |
| } | |
| } | |
| function updateVolume() { | |
| video.volume = volumeSlider.value; | |
| video.muted = false; | |
| if (video.volume === 0) { | |
| muteBtn.innerHTML = '<i class="fa-solid fa-volume-xmark"></i>'; | |
| } else if (video.volume < 0.5) { | |
| muteBtn.innerHTML = '<i class="fa-solid fa-volume-low"></i>'; | |
| } else { | |
| muteBtn.innerHTML = '<i class="fa-solid fa-volume-high"></i>'; | |
| } | |
| } | |
| // --- Fullscreen Logic --- | |
| function toggleFullscreen() { | |
| if (!document.fullscreenElement) { | |
| container.requestFullscreen().catch(err => { | |
| alert(`Error attempting to enable full-screen mode: ${err.message} (${err.name})`); | |
| }); | |
| } else { | |
| document.exitFullscreen(); | |
| } | |
| } | |
| // --- Pip Logic --- | |
| async function togglePiP() { | |
| try { | |
| if (video !== document.pictureInPictureElement) { | |
| await video.requestPictureInPicture(); | |
| } else { | |
| await document.exitPictureInPicture(); | |
| } | |
| } catch (error) { | |
| console.error("PiP Error:", error); | |
| } | |
| } | |
| // --- Event Listeners --- | |
| playPauseBtn.addEventListener('click', togglePlay); | |
| // Auto hide controls on mobile after 3 seconds of inactivity | |
| let controlsTimeout; | |
| const showControls = () => { | |
| document.getElementById('controlsOverlay').classList.add('active'); | |
| clearTimeout(controlsTimeout); | |
| // On mobile, keep controls visible longer or until play | |
| if(window.innerWidth <= 600) { | |
| // Don't auto hide on mobile to ensure usability | |
| } else { | |
| controlsTimeout = setTimeout(() => { | |
| if (!video.paused) { | |
| document.getElementById('controlsOverlay').classList.remove('active'); | |
| } | |
| }, 3000); | |
| } | |
| }; | |
| video.addEventListener('click', togglePlay); | |
| video.addEventListener('play', () => { | |
| updatePlayButtonIcon(); | |
| showControls(); | |
| }); | |
| video.addEventListener('pause', () => { | |
| updatePlayButtonIcon(); | |
| showControls(); | |
| }); | |
| video.addEventListener('timeupdate', updateProgress); | |
| video.addEventListener('loadedmetadata', () => { | |
| durationEl.textContent = formatTime(video.duration); | |
| }); | |
| progressContainer.addEventListener('click', setProgress); | |
| muteBtn.addEventListener('click', toggleMute); | |
| volumeSlider.addEventListener('input', updateVolume); | |
| fsBtn.addEventListener('click', toggleFullscreen); | |
| pipBtn.addEventListener('click', togglePiP); | |
| // Handle fullscreen changes | |
| document.addEventListener('fullscreenchange', () => { | |
| const icon = fsBtn.querySelector('i'); | |
| if (document.fullscreenElement) { | |
| icon.classList.remove('fa-expand'); | |
| icon.classList.add('fa-compress'); | |
| } else { | |
| icon.classList.remove('fa-compress'); | |
| icon.classList.add('fa-expand'); | |
| } | |
| }); | |
| // Handle File Upload | |
| uploadInput.addEventListener('change', function() { | |
| const file = this.files[0]; | |
| if (file) { | |
| const fileURL = URL.createObjectURL(file); | |
| video.src = fileURL; | |
| video.play(); | |
| updatePlayButtonIcon(); | |
| } | |
| }); | |
| // Keyboard Shortcuts | |
| document.addEventListener('keydown', (e) => { | |
| if (e.code === 'Space' && e.target.tagName !== 'INPUT') { | |
| e.preventDefault(); | |
| togglePlay(); | |
| } | |
| }); | |
| }); | |
| </script> | |
| </body> | |
| </html> |