<!-- big play button overlay --> <div class="big-play" id="bigPlayBtn">▶</div> <div class="loading-indicator" id="loadingIndicator">Loading...</div>
const container = document.getElementById('video-container'); const video = container.querySelector('.video'); const playPauseBtn = container.querySelector('.play-pause-btn'); const progressArea = container.querySelector('.progress-area'); const progressBar = container.querySelector('.progress-bar'); const volumeBtn = container.querySelector('.volume-btn'); const volumeSlider = container.querySelector('.volume-slider'); const currentTimeEl = container.querySelector('.current-time'); const durationEl = container.querySelector('.duration'); const speedBtn = container.querySelector('.speed-btn'); const pipBtn = container.querySelector('.pip-btn'); const fullscreenBtn = container.querySelector('.fullscreen-btn'); const controls = container.querySelector('.video-controls'); // 1. Play & Pause Functionality function togglePlay() if (video.paused) video.play(); else video.pause(); playPauseBtn.addEventListener('click', togglePlay); video.addEventListener('click', togglePlay); video.addEventListener('play', () => playPauseBtn.innerHTML = ''; startControlTimeout(); ); video.addEventListener('pause', () => playPauseBtn.innerHTML = ''; clearTimeout(controlsTimeout); controls.classList.remove('hidden'); ); // 2. Formatting Time function formatTime(time) const minutes = Math.floor(time / 60); const seconds = Math.floor(time % 60); return `$minutes:$seconds < 10 ? '0' : ''$seconds`; video.addEventListener('loadedmetadata', () => durationEl.textContent = formatTime(video.duration); controls.classList.remove('hidden'); // Show controls once ready ); // 3. Update Progress Bar & Current Time video.addEventListener('timeupdate', () => const percentage = (video.currentTime / video.duration) * 100; progressBar.style.width = `$percentage%`; currentTimeEl.textContent = formatTime(video.currentTime); ); // 4. Scrub / Click on Progress Bar to Skip progressArea.addEventListener('click', (e) => const rect = progressArea.getBoundingClientRect(); const clickX = e.clientX - rect.left; const width = rect.width; video.currentTime = (clickX / width) * video.duration; ); // 5. Volume Management volumeSlider.addEventListener('input', (e) => video.volume = e.target.value; video.muted = e.target.value == 0; updateVolumeIcon(); ); volumeBtn.addEventListener('click', () => video.muted = !video.muted; volumeSlider.value = video.muted ? 0 : video.volume; updateVolumeIcon(); ); function updateVolumeIcon() if (video.muted // 6. Playback Speed Control speedBtn.addEventListener('click', () => let currentSpeed = video.playbackRate; if (currentSpeed === 1) currentSpeed = 1.5; else if (currentSpeed === 1.5) currentSpeed = 2; else currentSpeed = 1; video.playbackRate = currentSpeed; speedBtn.textContent = `$currentSpeedx`; ); // 7. Picture-in-Picture (PiP) pipBtn.addEventListener('click', async () => try if (document.pictureInPictureElement) await document.exitPictureInPicture(); else await video.requestPictureInPicture(); catch (error) console.error("PiP failed", error); ); // 8. Fullscreen Toggle fullscreenBtn.addEventListener('click', () => if (!document.fullscreenElement) container.requestFullscreen().catch(err => alert(err.message)); fullscreenBtn.innerHTML = ''; else document.exitFullscreen(); fullscreenBtn.innerHTML = ''; ); // 9. Auto-Hide Controls on Inactivity let controlsTimeout; function startControlTimeout() if (video.paused) return; clearTimeout(controlsTimeout); controls.classList.remove('hidden'); controlsTimeout = setTimeout(() => controls.classList.add('hidden'); , 2500); container.addEventListener('mousemove', startControlTimeout); Use code with caution. Pro-Tips for Your CodePen Implementation
// Format time display const currentMinutes = Math.floor(video.currentTime / 60); const currentSeconds = Math.floor(video.currentTime % 60); const durationMinutes = Math.floor(video.duration / 60); const durationSeconds = Math.floor(video.duration % 60);
is the ideal sandbox for this project. It provides instant HTML/CSS/JS rendering, live preview, and easy sharing. By the end of this article, you will have a "Custom HTML5 Video Player Codepen" that you can fork, modify, or embed.
A custom player relies on a simple architectural division of labor: custom html5 video player codepen
// click on video toggles play/pause (optional UX) video.addEventListener('click', (e) => e.stopPropagation(); togglePlayPause(); );
<!-- progress & time --> <div class="progress-container"> <div class="progress-bar-bg" id="progressBar"> <div class="progress-fill" id="progressFill"></div> </div> <div class="time-display" id="timeDisplay">0:00 / 0:00</div> </div>
// big play button handler function onBigPlayClick() togglePlayPause();
In this guide, we will deconstruct how to build a fully functional, styled, and interactive custom video player from scratch. Best of all, we will prepare the code so it is ready to be dropped directly into for live experimentation. Update Progress Bar & Current Time video
<!-- Progress & time --> <div class="progress-area"> <span class="time-display" id="currentTimeUI">0:00</span> <div class="progress-bar-bg" id="progressBarBg"> <div class="progress-fill" id="progressFill"></div> </div> <span class="time-display" id="durationUI">0:00</span> </div>
This guide will walk you through building a modern, responsive, and custom HTML5 video player, providing a foundational structure you can adapt for your projects. Why Build a Custom HTML5 Video Player?
playPauseBtn.addEventListener('click', togglePlayPause); bigPlayBtn.addEventListener('click', onBigPlayClick); progressBar.addEventListener('click', seek); volumeSlider.addEventListener('input', () => video.volume = volumeSlider.value; updateVolume(); ); muteBtn.addEventListener('click', toggleMute); speedSelect.addEventListener('change', changeSpeed); fullscreenBtn.addEventListener('click', toggleFullscreen);
// update progress and time displays function updateProgress() if (video.duration && !isNaN(video.duration)) const percent = (video.currentTime / video.duration) * 100; progressFilled.style.width = `$percent%`; currentTimeSpan.innerText = formatTime(video.currentTime); else progressFilled.style.width = '0%'; currentTimeSpan.innerText = "0:00"; 0 : video
: Buttons for skipping forward or backward often use data-skip attributes to store the time increment in seconds. Aesthetic Control: CSS
// when video ends function onVideoEnded() updatePlayPauseUI(false); showBigPlayButtonIfNeeded(); wrapper.classList.remove('idle-controls'); // show controls when ended if (controlsTimeout) clearTimeout(controlsTimeout);
: Set width: 100% and height: auto on the video element to fit various screens. How to create a custom video player in JavaScript and HTML
I started by sketching the UI in my head: a rectangular stage with the video centered, a translucent control bar that hides when not needed, a prominent play/pause button, a fluid progress bar supporting scrubbing and buffered ranges, volume control with a subtle icon and vertical slider, captions toggle, and a fullscreen button. Accessibility mattered: keyboard control, focus outlines, and screen-reader labels.
body background: linear-gradient(145deg, #0b1a2e 0%, #0a111f 100%); min-height: 100vh; display: flex; justify-content: center; align-items: center; font-family: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif; padding: 1.5rem;
Notice how our CSS utilizes a .paused state helper? To elevate your player even further, use JavaScript to set a timer that adds an .inactive modifier class to hide the cursor and the UI panel when the mouse stops moving across the .video-container for more than 3 seconds.