Hey folks, Rahul here ๐
YouTube, Netflix, Disney+ โ video players look like a solved problem until you try building one. Custom controls, adaptive bitrate streaming, buffering strategies, Picture-in-Picture, keyboard shortcuts, and accessibility for deaf/blind users. The <video> tag gets you 10% of the way there.
R โ Requirements
Functional Requirements
- Play/pause, seek, volume, fullscreen controls
- Adaptive Bitrate Streaming (ABR) โ auto quality switching
- Manual quality selection (1080p, 720p, 480p, auto)
- Captions/subtitles with multiple tracks
- Playback speed control (0.5x โ 2x)
- Picture-in-Picture (PiP) mode
- Keyboard shortcuts (Space, F, M, โ/โ, โ/โ)
- Thumbnail preview on seek bar hover
- Resume from last position ("Continue watching")
Non-Functional Requirements
- Startup time: First frame within 2 seconds
- Buffering: Minimize rebuffering events to <1% of playback time
- Accessibility: Full keyboard control + screen reader announcements
- Mobile: Touch gestures (double-tap seek, swipe volume)
- Analytics: Buffer ratio, quality switches, engagement heatmap
A โ Architecture
Streaming Protocols
| Protocol | How It Works | Use When |
|---|---|---|
| HLS (HTTP Live Streaming) | Video split into .ts segments + .m3u8 manifest | Default choice. Safari native, rest via hls.js |
| DASH (MPEG-DASH) | Similar segmented approach + .mpd manifest | DRM required (Widevine), more flexible |
| Progressive MP4 | Single file, byte-range requests | Short videos (<5min), simple hosting |
Recommendation: HLS with hls.js. It covers 99% of use cases, works everywhere, and has excellent adaptive bitrate support.
Component Architecture
HLS.js Integration
D โ Data Model
Player State
I โ Interface Definition
Progress Bar with Buffer Visualization
Keyboard Shortcuts
O โ Optimizations
1. Controls Auto-Hide
2. Thumbnail Sprite Sheet Preview
3. Resume Playback Position
4. Engagement Analytics
Production Gotchas Rahul Has Debugged ๐ฅ
- Autoplay Policies: Modern browsers block autoplay with sound. Always start muted for autoplay, then unmute on user interaction. Check
video.play().catch()for theNotAllowedError. - iOS Fullscreen: iOS Safari forces native fullscreen controls for
<video>. Addplaysinlineattribute to use custom controls inline. PiP also requires user gesture to activate. - requestAnimationFrame for Time Updates: Don't rely on the
timeupdateevent โ it fires ~4x/second. UserequestAnimationFramefor smooth progress bar updates at 60fps. - Seek While Buffering: If the user seeks to an unbuffered position, HLS.js needs to flush the buffer and refetch. Show a loading spinner specifically for seek operations, not just for initial load.
- Memory Leaks: HLS.js accumulates source buffers. Call
hls.destroy()on unmount. Also revoke anyURL.createObjectURLreferences used for blob sources.
Next: #14: Design an Image Carousel / Gallery โ touch gestures, preloading strategies, virtualization for 1000+ images, and lightbox UX. ๐ผ๏ธ