-
Section 1: Getting Started
-
Section 2: Core Syntax and Types
-
Section 3: Strings and Numbers in Depth
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Objects and Arrays
-
Section 7: Maps, Sets, and Symbols
-
Section 8: Asynchronous JavaScript
-
Section 9: Object-Oriented and Prototypes
-
Section 10: The DOM
-
Section 11: Browser APIs
-
Section 12: Modern JavaScript (ES2015-ES2025)
-
Section 13: Functional Programming Patterns
-
Section 14: Error Handling and Debugging
-
Section 15: Testing
-
Section 16: Accessibility for JavaScript Developers
-
Section 17: Internationalization and Localization
-
Section 18: Performance
-
Section 19: Node.js Fundamentals
-
Section 20: Regular Expressions
-
Section 21: Design Patterns in JavaScript
-
Section 22: Security Basics
-
Section 23: Data Structures and Algorithms in JavaScript
-
Section 24: Practical Projects
-
Section 25: More Advanced Async Patterns
-
Section 26: More Object and Class Practice
-
Section 27: Working with Dates and Internationalization
-
Section 28: Web Components
-
Section 29: More DOM and Browser Practice
-
Section 30: Build Tooling for Vanilla JavaScript
-
Section 31: More Practice Projects
-
Section 32: Interview and Algorithm Practice
-
Section 33: Error Objects (MDN Reference)
-
Section 34: TypedArrays and Binary Data
-
Section 35: Reflection and Metaprogramming (MDN Reference)
-
Section 36: More Global Functions (MDN Reference)
225: Building a Custom Video Player Interface
When you first start building a custom video player, the instinct is usually to treat it like a standard web form. You build the buttons, you link them to the video element, and you assume the UI will just "know" when the video is moving. I see this mistake constantly: a developer builds a beautiful progress bar, but when they hit play, the bar just sits there at 0% while the video rolls on.
// The "It should just work" approach (Broken)
const video = document.querySelector('video');
const progressBar = document.querySelector('.progress-bar');
function updateProgress() {
const percentage = (video.currentTime / video.duration) * 100;
progressBar.style.width = percentage + '%';
}
// I'll just call this once when the page loads...
// or maybe in a setInterval?
updateProgress();
The Frozen Progress Bar
If you run the code above, you'll notice the progress bar never moves. Why? Because updateProgress is only called once. Even if you tried to wrap it in a setInterval, you'd be fighting the browser's internal clock, leading to stuttering movement or unnecessary CPU usage. The video element is its own state machine; it knows exactly where it is in the timeline, but it doesn't automatically push those updates to your custom HTML elements.
Syncing State with the timeupdate Event
To fix this, we need to stop trying to "guess" when the video has moved and instead listen to the video element itself. The HTML5 Video API provides a specific event called timeupdate. This event fires continuously as the playback position changes.
const video = document.querySelector('video');
const progressBar = document.querySelector('.progress-bar');
// Instead of a timer, we listen for the video's own heart-beat
video.addEventListener('timeupdate', () => {
const percentage = (video.currentTime / video.duration) * 100;
progressBar.style.width = `${percentage}%`;
});
Now the UI is reactive. The moment currentTime increments, the browser triggers the event, and your bar moves. I usually recommend using template literals (the backticks) for the width string—it's cleaner and prevents those annoying string concatenation bugs.
Handling the "Seek" Interaction
Moving the bar visually is only half the battle. You also want the user to be able to click the bar to jump to a specific part of the video. This is where a lot of people get tripped up by "infinite loops." If you update the video time when the slider moves, and the slider moves because the video time updated, you can end up with a feedback loop that makes the video jump erratically.
The trick is to separate the visual update (driven by the video) from the user input (driven by the mouse). If you're using an <input type="range">, you should listen for the input event to update the video's currentTime.
const seekSlider = document.querySelector('#seek-slider');
seekSlider.addEventListener('input', () => {
// Calculate the new time based on the slider's current value
const time = (seekSlider.value / 100) * video.duration;
video.currentTime = time;
});
One last pro tip: never try to access video.duration the millisecond the page loads. The video metadata (like how long the file is) often loads asynchronously. If you try to calculate a percentage before the metadata is ready, you'll get NaN because you're dividing by NaN. Always wrap your initial setup in a loadedmetadata event listener if you're doing calculations on startup.
📋 Practical Task
Build a Synchronized Volume and Mute Controller
Using the concepts of event listeners and the Video API, extend a custom video player by adding a volume control system. Your task is to implement the following functionality:
- Create a volume slider (an
<input type="range">) that adjusts thevideo.volumeproperty in real-time. - Create a "Mute/Unmute" button that toggles the
video.mutedboolean. - The Challenge: Ensure that when the user adjusts the volume slider, the video is automatically unmuted. Conversely, if the user clicks the "Mute" button, the volume slider's visual position should update to reflect that the volume is effectively zero, without actually changing the stored volume level (so when they unmute, it returns to the previous volume).
There are no comments for now.