In the neon-lit corridors of "The Daily Scroll," a bustling digital agency, sat Leo, a front-end developer who had just been handed a nightmare. His client, a high-end luxury watch brand, didn't want a "standard" YouTube embed. They wanted a video player that felt like one of their timepieces: sleek, custom, and frictionless.
Leo opened CodePen, his digital sandbox, and started with the skeleton. He skipped the default browser controls—those clunky gray bars wouldn't do. Instead, he wrapped a standard tag in a custom container, hidden away like the inner gears of a watch.
Using CSS Flexbox, Leo forged a control bar that floated elegantly at the bottom. He styled the play button as a minimalist gold triangle and the progress bar as a thin, silk-like thread that glowed as it moved.
Then came the magic: JavaScript. Leo wrote a few lines of "event listeners" to act as the player's pulse.
video.play() and video.pause() were tied to his custom gold button.
He calculated the currentTime versus duration to make the progress thread grow in real-time.
He even added a "scrub" feature, allowing users to drag the thread to any second of the film.
By midnight, Leo hit "Save." He didn't just have a video player; he had a masterpiece. He shared the CodePen link with the client, and as the smooth, custom-coded interface glided across their screens, he knew he’d turned a simple HTML5 tag into a premium experience.
Creating a custom HTML5 video player allows you to match your site's branding and provide a unique user experience. By using the HTML5 Media API, you can replace browser-default controls with your own buttons, sliders, and progress bars. 🛠️ The Core Components Building a custom player requires three distinct layers:
HTML: Defines the video container and the control interface. CSS: Styles the layout, buttons, and responsive behavior.
JavaScript: Hooks into the video events (play, pause, volume) to update the UI. 🏗️ Step 1: Markup (HTML)
Wrap your tag and custom controls in a wrapper. This ensures you can hide the default controls and position your UI over the video.
Use code with caution. Copied to clipboard 🎨 Step 2: Styling (CSS)
Use CSS Flexbox or Grid to align your controls. Hide the native controls by omitting the controls attribute in HTML and use position: absolute to overlay your custom bar. Overlay: Put controls at the bottom of the container. Z-index: Ensure controls sit above the video layer.
Custom Sliders: Use input[type="range"] for progress and volume. ⚙️ Step 3: Logic (JavaScript)
This is where the magic happens. You need to listen for user clicks and video updates. Toggle Play: Use video.play() and video.pause(). Update Progress: Listen to the timeupdate event.
Scrubbing: Update video.currentTime when the progress slider moves. Volume: Map the volume slider value to video.volume. 🚀 Interactive Examples on CodePen
For live code and visual inspiration, check out these popular implementations: Clean & Minimal Player: Great for portfolio sites. Plyr.io Clone: A lightweight, accessible HTML5 player.
Netflix-style UI: Features custom overlays and big play icons. custom html5 video player codepen
📌 Pro Tip: Always include a "Mute" button. Autoplay videos often require the muted attribute to function in modern browsers like Chrome and Safari.
If you'd like, I can write the full source code (HTML, CSS, and JS) for a specific style, like a minimalist dark theme or a glassmorphism player. Which one would you prefer?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>Custom HTML5 Video Player | Modern UI</title>
<style>
*
margin: 0;
padding: 0;
box-sizing: border-box;
user-select: none; /* avoid accidental selection on double-click */
body
background: linear-gradient(145deg, #1a1e2c 0%, #11141f 100%);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
font-family: 'Segoe UI', 'Poppins', system-ui, -apple-system, 'Inter', sans-serif;
padding: 20px;
/* MAIN PLAYER CARD */
.player-container
max-width: 1000px;
width: 100%;
background: rgba(0, 0, 0, 0.65);
backdrop-filter: blur(2px);
border-radius: 32px;
box-shadow: 0 25px 45px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(255, 255, 255, 0.08);
overflow: hidden;
transition: all 0.2s ease;
/* video wrapper (for custom controls overlay) */
.video-wrapper
position: relative;
background: #000;
width: 100%;
cursor: pointer;
video
width: 100%;
height: auto;
display: block;
vertical-align: middle;
/* ----- CUSTOM CONTROLS BAR (modern glass) ----- */
.custom-controls
background: rgba(20, 22, 36, 0.85);
backdrop-filter: blur(12px);
padding: 12px 18px;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 12px;
border-top: 1px solid rgba(255, 255, 255, 0.15);
transition: opacity 0.25s ease;
font-size: 14px;
/* left group */
.controls-left
display: flex;
align-items: center;
gap: 14px;
flex: 2;
/* center group (progress) */
.controls-center
flex: 6;
min-width: 140px;
/* right group */
.controls-right
display: flex;
align-items: center;
gap: 18px;
flex: 2;
justify-content: flex-end;
/* buttons styling */
.ctrl-btn
background: transparent;
border: none;
color: #f0f0f0;
font-size: 20px;
width: 36px;
height: 36px;
border-radius: 50%;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
backdrop-filter: blur(4px);
.ctrl-btn:hover
background: rgba(255, 255, 255, 0.2);
transform: scale(1.02);
.ctrl-btn:active
transform: scale(0.96);
/* time display */
.time-display
font-family: 'Monaco', 'Fira Mono', monospace;
font-size: 0.9rem;
background: rgba(0, 0, 0, 0.5);
padding: 5px 10px;
border-radius: 40px;
letter-spacing: 0.5px;
color: #eef;
/* volume slider container */
.volume-wrap
display: flex;
align-items: center;
gap: 8px;
.volume-icon
font-size: 20px;
cursor: pointer;
background: none;
border: none;
color: #f0f0f0;
display: inline-flex;
align-items: center;
input[type="range"]
-webkit-appearance: none;
background: transparent;
cursor: pointer;
/* progress bar (seek) */
.progress-bar
flex: 1;
height: 5px;
background: rgba(255, 255, 255, 0.25);
border-radius: 20px;
position: relative;
cursor: pointer;
transition: height 0.1s;
.progress-bar:hover
height: 7px;
.progress-filled
width: 0%;
height: 100%;
background: linear-gradient(90deg, #e14eca, #d6409f, #ff7b89);
border-radius: 20px;
position: relative;
pointer-events: none;
.progress-filled::after
content: '';
position: absolute;
right: -6px;
top: 50%;
transform: translateY(-50%);
width: 12px;
height: 12px;
background: #ffb3d9;
border-radius: 50%;
box-shadow: 0 0 6px #ff80b3;
opacity: 0;
transition: opacity 0.1s;
.progress-bar:hover .progress-filled::after
opacity: 1;
/* volume range style */
.volume-slider
width: 80px;
height: 4px;
background: rgba(255, 255, 255, 0.3);
border-radius: 5px;
input[type="range"]::-webkit-slider-thumb
-webkit-appearance: none;
width: 12px;
height: 12px;
background: white;
border-radius: 50%;
cursor: pointer;
box-shadow: 0 0 2px #fff;
border: none;
/* speed dropdown */
.speed-select
background: rgba(0, 0, 0, 0.6);
border: 1px solid rgba(255, 255, 255, 0.3);
color: white;
padding: 6px 10px;
border-radius: 32px;
font-size: 0.8rem;
font-weight: 500;
cursor: pointer;
outline: none;
backdrop-filter: blur(4px);
transition: 0.1s;
.speed-select:hover
background: rgba(30, 30, 50, 0.9);
/* fullscreen button */
.fullscreen-btn
font-size: 20px;
/* responsive adjustments */
@media (max-width: 680px)
.custom-controls
flex-wrap: wrap;
gap: 10px;
padding: 12px;
.controls-left, .controls-right
flex: auto;
.controls-center
order: 3;
flex: 1 1 100%;
margin-top: 6px;
.volume-slider
width: 60px;
.ctrl-btn
width: 32px;
height: 32px;
font-size: 18px;
.time-display
font-size: 0.75rem;
/* loading / error / poster style */
.video-wrapper .loading-indicator
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(0,0,0,0.7);
backdrop-filter: blur(6px);
padding: 10px 20px;
border-radius: 40px;
color: white;
font-size: 14px;
pointer-events: none;
opacity: 0;
transition: opacity 0.2s;
z-index: 10;
/* big play button overlay */
.big-play
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 70px;
height: 70px;
background: rgba(0,0,0,0.6);
backdrop-filter: blur(10px);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 38px;
cursor: pointer;
transition: all 0.2s ease;
opacity: 0;
z-index: 15;
pointer-events: auto;
border: 1px solid rgba(255,255,255,0.3);
.big-play:hover
background: #e14eca;
transform: translate(-50%, -50%) scale(1.05);
color: white;
/* fade animations for controls hide/show */
.controls-hidden .custom-controls
opacity: 0;
visibility: hidden;
transition: visibility 0.2s, opacity 0.2s;
.video-wrapper:hover .custom-controls
opacity: 1;
visibility: visible;
/* default: visible, but on idle we hide via class toggled by js */
.custom-controls
visibility: visible;
transition: opacity 0.3s ease, visibility 0.3s;
/* mouse idle (no movement) - class added by js */
.idle-controls .custom-controls
opacity: 0;
visibility: hidden;
/* but on hover always show regardless of idle */
.video-wrapper:hover .custom-controls
opacity: 1 !important;
visibility: visible !important;
/* big play button also hides when playing */
.big-play.hide-big
display: none;
</style>
</head>
<body>
<div class="player-container">
<div class="video-wrapper" id="videoWrapper">
<video id="myVideo" poster="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/images/BigBuckBunny.jpg" preload="metadata">
<!-- sample video from sample-videos.com / big buck bunny (high quality) -->
<source src="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4" type="video/mp4">
Your browser does not support HTML5 video.
</video>
<!-- big play button overlay -->
<div class="big-play" id="bigPlayBtn">▶</div>
<div class="loading-indicator" id="loadingIndicator">Loading...</div>
<!-- custom control bar -->
<div class="custom-controls" id="customControls">
<div class="controls-left">
<button class="ctrl-btn" id="playPauseBtn" aria-label="Play/Pause">⏸</button>
<div class="volume-wrap">
<button class="volume-icon" id="muteBtn" aria-label="Mute">🔊</button>
<input type="range" id="volumeSlider" class="volume-slider" min="0" max="1" step="0.01" value="1">
</div>
<div class="time-display">
<span id="currentTime">0:00</span> / <span id="duration">0:00</span>
</div>
</div>
<div class="controls-center">
<div class="progress-bar" id="progressBar">
<div class="progress-filled" id="progressFilled"></div>
</div>
</div>
<div class="controls-right">
<select id="speedSelect" class="speed-select">
<option value="0.5">0.5x</option>
<option value="0.75">0.75x</option>
<option value="1" selected>1x</option>
<option value="1.25">1.25x</option>
<option value="1.5">1.5x</option>
<option value="2">2x</option>
</select>
<button class="ctrl-btn fullscreen-btn" id="fullscreenBtn" aria-label="Fullscreen">⛶</button>
</div>
</div>
</div>
</div>
<script>
(function() {
// DOM elements
const video = document.getElementById('myVideo');
const wrapper = document.getElementById('videoWrapper');
const playPauseBtn = document.getElementById('playPauseBtn');
const bigPlayBtn = document.getElementById('bigPlayBtn');
const progressBar = document.getElementById('progressBar');
const progressFilled = document.getElementById('progressFilled');
const currentTimeSpan = document.getElementById('currentTime');
const durationSpan = document.getElementById('duration');
const volumeSlider = document.getElementById('volumeSlider');
const muteBtn = document.getElementById('muteBtn');
const speedSelect = document.getElementById('speedSelect');
const fullscreenBtn = document.getElementById('fullscreenBtn');
const loadingIndicator = document.getElementById('loadingIndicator');
// state
let controlsTimeout = null;
let isControlsIdle = false;
let isPlaying = false;
// Helper: format time (seconds to MM:SS)
function formatTime(seconds)
if (isNaN(seconds)) return "0:00";
const hrs = Math.floor(seconds / 3600);
const mins = Math.floor((seconds % 3600) / 60);
const secs = Math.floor(seconds % 60);
if (hrs > 0)
return `$hrs:$mins.toString().padStart(2, '0'):$secs.toString().padStart(2, '0')`;
return `$mins:$secs.toString().padStart(2, '0')`;
// 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";
// update duration display
function updateDuration()
if (video.duration && !isNaN(video.duration))
durationSpan.innerText = formatTime(video.duration);
else
durationSpan.innerText = "0:00";
// play/pause toggles + big play button sync
function togglePlayPause()
if (video.paused
function updatePlayPauseUI(playing)
isPlaying = playing;
if (playing)
playPauseBtn.innerHTML = "⏸";
playPauseBtn.setAttribute("aria-label", "Pause");
else
playPauseBtn.innerHTML = "▶";
playPauseBtn.setAttribute("aria-label", "Play");
function hideBigPlayButton()
bigPlayBtn.classList.add('hide-big');
function showBigPlayButtonIfNeeded()
if (video.paused && !video.ended)
bigPlayBtn.classList.remove('hide-big');
else
bigPlayBtn.classList.add('hide-big');
// seek using progress bar
function seek(e)
const rect = progressBar.getBoundingClientRect();
let clickX = e.clientX - rect.left;
let width = rect.width;
if (width > 0 && video.duration)
const percent = Math.min(Math.max(clickX / width, 0), 1);
video.currentTime = percent * video.duration;
updateProgress();
// volume
function updateVolume()
video.volume = volumeSlider.value;
if (video.volume === 0)
muteBtn.innerHTML = "🔇";
else if (video.volume < 0.5)
muteBtn.innerHTML = "🔉";
else
muteBtn.innerHTML = "🔊";
function toggleMute()
if (video.volume === 0)
video.volume = volumeSlider.value = 0.5;
else
video.volume = 0;
volumeSlider.value = 0;
updateVolume();
// speed change
function changeSpeed()
video.playbackRate = parseFloat(speedSelect.value);
// fullscreen (modern api)
function toggleFullscreen()
const elem = wrapper;
if (!document.fullscreenElement)
if (elem.requestFullscreen)
elem.requestFullscreen().catch(err =>
console.warn(`Fullscreen error: $err.message`);
);
else if (elem.webkitRequestFullscreen)
elem.webkitRequestFullscreen();
else if (elem.msRequestFullscreen)
elem.msRequestFullscreen();
else
document.exitFullscreen();
// idle controls (hide after mouse inactivity)
function resetControlsIdleTimer()
if (controlsTimeout) clearTimeout(controlsTimeout);
if (wrapper.classList.contains('idle-controls'))
wrapper.classList.remove('idle-controls');
controlsTimeout = setTimeout(() =>
// only if video is playing and mouse not over wrapper (but we also will check hover)
// we add idle class only if playing, else keep controls visible.
if (!video.paused && !video.ended)
wrapper.classList.add('idle-controls');
else
// if paused, we do not hide controls
wrapper.classList.remove('idle-controls');
, 2000);
// event listeners for idle management
function initIdleHandling()
wrapper.addEventListener('mousemove', resetControlsIdleTimer);
wrapper.addEventListener('mouseleave', () =>
if (controlsTimeout) clearTimeout(controlsTimeout);
if (!video.paused && !video.ended)
wrapper.classList.add('idle-controls');
else
wrapper.classList.remove('idle-controls');
);
wrapper.addEventListener('mouseenter', () =>
wrapper.classList.remove('idle-controls');
resetControlsIdleTimer();
);
resetControlsIdleTimer();
// loading spinner handling
function handleLoadingStart()
loadingIndicator.style.opacity = '1';
function handleCanPlay()
loadingIndicator.style.opacity = '0';
updateDuration();
updateProgress();
function handleWaiting()
loadingIndicator.style.opacity = '1';
function handlePlaying()
loadingIndicator.style.opacity = '0';
// big play button handler
function onBigPlayClick()
togglePlayPause();
// keyboard shortcuts (space, k, f)
function handleKeyPress(e) tag === 'SELECT'
// when video ends
function onVideoEnded()
updatePlayPauseUI(false);
showBigPlayButtonIfNeeded();
wrapper.classList.remove('idle-controls'); // show controls when ended
if (controlsTimeout) clearTimeout(controlsTimeout);
// when video starts playing
function onVideoPlay()
updatePlayPauseUI(true);
hideBigPlayButton();
resetControlsIdleTimer();
function onVideoPause()
updatePlayPauseUI(false);
showBigPlayButtonIfNeeded();
wrapper.classList.remove('idle-controls'); // force controls visible on pause
if (controlsTimeout) clearTimeout(controlsTimeout);
// event binding
video.addEventListener('loadedmetadata', () =>
updateDuration();
updateProgress();
);
video.addEventListener('timeupdate', updateProgress);
video.addEventListener('play', onVideoPlay);
video.addEventListener('playing', () => loadingIndicator.style.opacity = '0'; );
video.addEventListener('pause', onVideoPause);
video.addEventListener('ended', onVideoEnded);
video.addEventListener('waiting', handleWaiting);
video.addEventListener('canplay', handleCanPlay);
video.addEventListener('loadstart', handleLoadingStart);
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);
// additional double click on video toggles fullscreen?
video.addEventListener('dblclick', () =>
toggleFullscreen();
);
// click on video toggles play/pause (optional UX)
video.addEventListener('click', (e) =>
e.stopPropagation();
togglePlayPause();
);
// handle volume init
updateVolume();
// set initial play button icon because video is initially paused (showing poster)
updatePlayPauseUI(false);
// show big play button initially because video is paused
bigPlayBtn.classList.remove('hide-big');
// if video is already loaded (cached) ensure duration shown
if (video.readyState >= 1)
updateDuration();
updateProgress();
// Fix potential Firefox/Edge issues: set default speed
video.playbackRate = 1;
// idle controls handler init
initIdleHandling();
// prevent context menu on video for cleaner UX (optional)
video.addEventListener('contextmenu', (e) => e.preventDefault());
// Additional small improvement: when seeking via progress bar show time
progressBar.addEventListener('mousemove', (e) =>
// optional tooltip preview (nice to have but not mandatory)
);
// ensure that if video duration changes (livestream not needed)
window.addEventListener('resize', () => {});
console.log('Custom video player ready!');
})();
</script>
</body>
</html>
A professional custom player supports keyboard navigation. Add this block to your JavaScript:
document.addEventListener('keydown', (e) =>
const tag = e.target.tagName.toLowerCase();
if (tag === 'input' );
Now users can press Space to pause, Arrow keys to seek ±5 seconds, F for fullscreen, and M to mute.
One of the most critical, yet often overlooked, aspects of a custom video player is accessibility. Native browser controls come with built-in screen reader support and keyboard navigation. When a developer strips these away to inject a custom UI, they are responsible for restoring that accessibility.
A well-coded CodePen example will demonstrate the use of ARIA (Accessible Rich Internet Applications) attributes. The custom play button, which might just be an <i> tag visually, must include role="button" and aria-label="Play". The progress bar needs role="slider" and updated aria-valuenow attributes as the video plays. Writing an accessible custom player requires the developer to think not just about how the player looks, but how it communicates with assistive technologies. It transforms the coding process from a purely visual task into a structural and semantic responsibility.
<custom-player>).Ready to level up? Open CodePen, paste the code above, and start customizing. Your perfect video player is just a few keystrokes away.
Creating a custom HTML5 video player on CodePen involves replacing the default browser controls with a tailored UI built using HTML, CSS, and JavaScript Key Features of a Custom Player
A standard custom build typically includes the following interactive elements Custom Play/Pause Button
: Replaces the default browser icon with a themed button that toggles the video state via JavaScript Dynamic Progress Bar
: A "seeking" bar that allows users to jump to different parts of the video and visually tracks playback progress Volume & Mute Controls
: Custom sliders for granular volume adjustment and a toggle to quickly silence audio Time Tracking
: Real-time displays for both the current timestamp and the total duration of the video Custom Styling (CSS)
: The ability to skin the player to match a brand's aesthetic, which is not possible with the standard Implementation in CodePen
, you can experiment with these features using the three-panel editor tag without the attribute. Add elements for your custom interface
: Style your buttons and the progress bar to overlay or sit below the video container JavaScript : Use the HTML5 Video API (e.g., .currentTime ) to link your custom UI elements to the video's behavior minimal code template to start your own custom player on CodePen? Getting Started with CodePen: A Beginner's Guide to CodePen
CodePen works is you by default get three different sheets you get an HTML sheet a CSS sheet and a JavaScript sheet. colearnable
How to create a custom video player in JavaScript and HTML - Uploadcare In the neon-lit corridors of "The Daily Scroll,"
The Project
I had always been fascinated by the possibilities of HTML5 video players. With the rise of online video content, it seemed like a great opportunity to create something unique and interactive. I decided to challenge myself to build a custom HTML5 video player from scratch using CodePen, a popular online code editor.
The Design
Before diving into code, I spent some time researching existing video players and thinking about the features I wanted to include in my player. I wanted it to be modern, sleek, and easy to use. I sketched out a basic design, which included:
The Code
I started by creating a new pen on CodePen and setting up the basic HTML structure:
<div class="video-container">
<video id="video" src="https://example.com/video.mp4" poster="https://example.com/poster.jpg"></video>
<div class="controls">
<button id="play-pause" class="btn">Play/Pause</button>
<progress id="progress" value="0" max="100"></progress>
<input id="volume" type="range" min="0" max="1" step="0.1" value="0.5">
<button id="fullscreen" class="btn">Fullscreen</button>
</div>
</div>
Next, I added some basic CSS to style the player:
.video-container
width: 640px;
margin: 40px auto;
background-color: #f9f9f9;
border: 1px solid #ddd;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
.video-container video
width: 100%;
height: 360px;
object-fit: cover;
.controls
padding: 10px;
background-color: #fff;
border-top: 1px solid #ddd;
.btn
background-color: #4CAF50;
color: #fff;
border: none;
padding: 10px 20px;
font-size: 16px;
cursor: pointer;
.btn:hover
background-color: #3e8e41;
progress
width: 100%;
height: 10px;
margin: 10px 0;
border: 1px solid #ddd;
#volume
width: 100px;
height: 10px;
margin: 10px 0;
The JavaScript
Now it was time to add the JavaScript code to make the player functional. I started by getting references to the HTML elements:
const video = document.getElementById('video');
const playPauseButton = document.getElementById('play-pause');
const progressBar = document.getElementById('progress');
const volumeInput = document.getElementById('volume');
const fullscreenButton = document.getElementById('fullscreen');
Next, I added event listeners to the buttons:
playPauseButton.addEventListener('click', () =>
if (video.paused)
video.play();
playPauseButton.textContent = 'Pause';
else
video.pause();
playPauseButton.textContent = 'Play';
);
fullscreenButton.addEventListener('click', () =>
if (document.fullscreenElement)
document.exitFullscreen();
else
video.requestFullscreen();
);
volumeInput.addEventListener('input', () =>
video.volume = volumeInput.value;
);
video.addEventListener('timeupdate', () =>
const progress = (video.currentTime / video.duration) * 100;
progressBar.value = progress;
);
video.addEventListener('ended', () =>
playPauseButton.textContent = 'Play';
);
The Polish
After testing the player, I realized that it needed a few more features to make it more user-friendly. I added a few more lines of CSS to make the player more responsive:
.video-container
max-width: 100%;
margin: 20px auto;
.video-container video
height: auto;
I also added a simple animation to the play/pause button:
.btn
transition: background-color 0.2s ease-in-out;
.btn:hover
transition: background-color 0.2s ease-in-out;
The Result
After several hours of coding, I had a fully functional custom HTML5 video player. It was responsive, interactive, and had all the features I wanted. I was proud of what I had accomplished and couldn't wait to share it with others.
CodePen
I pushed my code to CodePen and shared it with the community. I got a lot of great feedback and even a few suggestions for new features. It was a great experience and I learned a lot from it.
Conclusion
Creating a custom HTML5 video player using CodePen was a fun and rewarding experience. It allowed me to explore the possibilities of HTML5 video and create something unique and interactive. I hope that my story will inspire others to try building their own custom video players. Who knows what amazing things you'll create? Step 4: Adding Keyboard Shortcuts A professional custom
Here is the complete code:
HTML:
<div class="video-container">
<video id="video" src="https://example.com/video.mp4" poster="https://example.com/poster.jpg"></video>
<div class="controls">
<button id="play-pause" class="btn">Play/Pause</button>
<progress id="progress" value="0" max="100"></progress>
<input id="volume" type="range" min="0" max="1" step="0.1" value="0.5">
<button id="fullscreen" class="btn">Fullscreen</button>
</div>
</div>
CSS:
.video-container
width: 640px;
margin: 40px auto;
background-color: #f9f9f9;
border: 1px solid #ddd;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
.video-container video
width: 100%;
height: 360px;
object-fit: cover;
.controls
padding: 10px;
background-color: #fff;
border-top: 1px solid #ddd;
.btn
background-color: #4CAF50;
color: #fff;
border: none;
padding: 10px 20px;
font-size: 16px;
cursor: pointer;
.btn:hover
background-color: #3e8e41;
progress
width: 100%;
height: 10px;
margin: 10px 0;
border: 1px solid #ddd;
#volume
width: 100px;
height: 10px;
margin: 10px 0;
JavaScript:
const video = document.getElementById('video');
const playPauseButton = document.getElementById('play-pause');
const progressBar = document.getElementById('progress');
const volumeInput = document.getElementById('volume');
const fullscreenButton = document.getElementById('fullscreen');
playPauseButton.addEventListener('click', () =>
if (video.paused)
video.play();
playPauseButton.textContent = 'Pause';
else
video.pause();
playPauseButton.textContent = 'Play';
);
fullscreenButton.addEventListener('click', () =>
if (document.fullscreenElement)
document.exitFullscreen();
else
video.requestFullscreen();
);
volumeInput.addEventListener('input', () =>
video.volume = volumeInput.value;
);
video.addEventListener('timeupdate', () =>
const progress = (video.currentTime / video.duration) * 100;
progressBar.value = progress;
);
video.addEventListener('ended', () =>
playPauseButton.textContent = 'Play';
);
This is the most critical section. We will use the HTML5 Media API to link the buttons to video functions.
// Get DOM elements const video = document.getElementById('myVideo'); const playPauseBtn = document.getElementById('playPauseBtn'); const progressBar = document.querySelector('.progress-bar'); const progressFill = document.getElementById('progressFill'); const timeDisplay = document.getElementById('timeDisplay'); const volumeSlider = document.getElementById('volumeSlider'); const fullscreenBtn = document.getElementById('fullscreenBtn');// 1. Play / Pause Logic function togglePlayPause() video.ended) video.play(); playPauseBtn.textContent = '⏸ Pause'; else video.pause(); playPauseBtn.textContent = '▶ Play';
playPauseBtn.addEventListener('click', togglePlayPause);
// 2. Update Progress Bar and Time as video plays video.addEventListener('timeupdate', () => const percentage = (video.currentTime / video.duration) * 100; progressFill.style.width =
$percentage%;// 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);
timeDisplay.textContent =
$currentMinutes.toString().padStart(2, '0'):$currentSeconds.toString().padStart(2, '0') / $durationMinutes.toString().padStart(2, '0'):$durationSeconds.toString().padStart(2, '0'); );// 3. Seek Video when clicking on progress bar progressBar.addEventListener('click', (e) => const rect = progressBar.getBoundingClientRect(); const clickX = e.clientX - rect.left; const width = rect.width; const clickPercent = clickX / width; video.currentTime = clickPercent * video.duration; );
// 4. Volume Control volumeSlider.addEventListener('input', (e) => video.volume = e.target.value; );
// 5. Fullscreen functionality fullscreenBtn.addEventListener('click', () => const container = document.querySelector('.video-container'); if (!document.fullscreenElement) container.requestFullscreen(); else document.exitFullscreen(); );
// Optional: Auto-update play/pause button if video ends video.addEventListener('ended', () => playPauseBtn.textContent = '▶ Play'; );
JavaScript breakdown:
timeupdate event fires every 250ms, updating the red progress bar and the timestamp.clientX and getBoundingClientRect) and jumps the currentTime.requestFullscreen makes the entire container go fullscreen, not just the video.CodePen is the ideal sandbox for a custom video player because:
Now, let’s build.