Project 2: Accessible Video Player
📋 Project Brief
Build a fully custom video player that replaces native browser controls with a keyboard-navigable, screen-reader-friendly interface. This project is a deep dive into the HTML5 Media API, WAI-ARIA standards, and Universal Design principles. Every user — regardless of motor or visual ability — must be able to control playback with the same level of precision.
🎯 Learning Objectives
- Master the HTML5
<video>element's JavaScript API (play, pause, currentTime, volume, playbackRate) - Implement comprehensive keyboard navigation following WAI-ARIA media player guidelines
- Build custom UI controls that communicate state to assistive technologies via ARIA attributes
- Handle complex edge cases: fullscreen transitions, Picture-in-Picture, and buffering states
- Create a progress bar with precise scrubbing that works with both mouse and keyboard
📐 Architecture Overview
| Layer | Responsibility | Key Challenge |
|---|---|---|
| Media Engine | Wraps the native <video> element and exposes a clean API | Synchronizing UI state with asynchronous media events |
| Control Bar | Custom play/pause, volume, progress, and settings controls | Making every control accessible via keyboard and screen reader |
| Caption System | Renders and announces subtitle tracks | Styling WebVTT cues and announcing changes to screen readers |
| Keyboard Manager | Maps keyboard shortcuts to media actions | Preventing conflicts with browser-native shortcuts |
✅ Feature Requirements
Feature 1: Keyboard Shortcuts
What it does: Maps specific keys to media actions, mimicking YouTube's keyboard UX.
Acceptance Criteria:
- Space or K → Toggle play/pause
- J → Seek backward 10 seconds
- L → Seek forward 10 seconds
- M → Toggle mute
- F → Toggle fullscreen
- ↑ / ↓ → Increase / decrease volume by 5%
- ← / → → Seek backward / forward 5 seconds
- All shortcuts must ONLY work when the player has focus (not globally on the page)
- A "Keyboard Shortcuts" help overlay must be toggleable with ?
Feature 2: ARIA-Live Captions
What it does: A dedicated region that visually displays captions and announces track changes to screen readers.
Acceptance Criteria:
- Support loading
.vttcaption files - Display captions in a styled overlay at the bottom of the video
- When a new caption track is selected, announce "Captions changed to [Language]" via an
aria-live="polite"region - Provide a "Captions Off" option
- Caption font size must be adjustable (Small, Medium, Large)
Feature 3: Picture-in-Picture (PiP)
What it does: Allows the video to float in a small window while the user browses other content.
Acceptance Criteria:
- A button in the control bar triggers the browser's native PiP API
- The button icon must change to reflect the current PiP state (enter/exit)
- Gracefully handle browsers that don't support PiP (hide the button entirely)
- When PiP is active, the main player area should show a "Playing in Picture-in-Picture" message
Feature 4: Playback Speed Controller
What it does: A slider or menu to adjust playback rate from 0.25x to 3x.
Acceptance Criteria:
- Offer preset speeds: 0.25x, 0.5x, 0.75x, 1x, 1.25x, 1.5x, 2x, 3x
- Include a "Preserve Pitch" toggle (uses
preservesPitchproperty) - The current speed must be visually indicated and announced to screen readers
- Changing speed must not interrupt or restart playback
Feature 5: Buffer Progress Visualizer
What it does: Shows how much of the video has been downloaded/cached by the browser.
Acceptance Criteria:
- A secondary bar (behind the playback progress bar) shows buffered ranges
- Must handle multiple buffered ranges (the
bufferedproperty returns aTimeRangesobject) - The buffer bar must be visually distinct from the playback bar (e.g., lighter color)
- Hovering over the progress bar shows a time tooltip at the cursor position
♿ Accessibility Requirements (Non-Negotiable)
| Requirement | Standard | How to Verify |
|---|---|---|
| All controls have visible focus indicators | WCAG 2.4.7 | Tab through every control — each must show a clear outline |
| All controls have accessible names | WCAG 4.1.2 | Use axe-core or screen reader to verify labels |
| Progress bar is an accessible slider | ARIA slider pattern | Must have role="slider", aria-valuenow, aria-valuemin, aria-valuemax |
| State changes are announced | WCAG 4.1.3 | "Paused", "Playing", "Muted" announced via aria-live |
| No keyboard traps | WCAG 2.1.2 | User must be able to Tab out of the player at any time |
🏗️ Suggested File Structure
| File / Folder | Purpose |
|---|---|
src/components/VideoPlayer/ | Main player wrapper |
src/components/ControlBar/ | Play, volume, progress, settings controls |
src/components/CaptionOverlay/ | Caption rendering and track selection |
src/hooks/useMediaPlayer.js | Custom hook wrapping the video element API |
src/hooks/useKeyboardShortcuts.js | Keyboard event handler with scope management |
src/utils/timeFormat.js | Converts seconds to MM:SS or HH:MM:SS |
🧪 Testing Checklist
- Unit test time formatting utility (0 → "0:00", 3661 → "1:01:01")
- Unit test keyboard mapping logic
- Integration test: pressing Space toggles between play and pause states
- A11y audit: zero violations using axe-core
- Cross-browser test: verify PiP graceful degradation in Firefox vs Chrome
🚀 Stretch Goals
- Add a "Chapters" system that shows named segments on the progress bar
- Implement "Theater Mode" that expands the player to full-width
- Add gesture support for mobile (swipe left/right to seek, swipe up/down for volume)
- Create a "Mini Player" mode that sticks to the bottom-right on scroll