
Quick Start
1. Basic Audio Playlist (30 seconds)
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="vidply.css">
</head>
<body>
<div id="player"></div>
<script type="module">
import { Player, PlaylistManager } from 'vidply';
// 1. Create player
const player = new Player('#player');
// 2. Create playlist
const playlist = new PlaylistManager(player, {
autoAdvance: true,
showPanel: true
});
// 3. Load tracks
playlist.loadPlaylist([
{ src: 'song1.mp3', type: 'audio/mp3', title: 'Song 1', artist: 'Artist 1' },
{ src: 'song2.mp3', type: 'audio/mp3', title: 'Song 2', artist: 'Artist 2' },
{ src: 'song3.mp3', type: 'audio/mp3', title: 'Song 3', artist: 'Artist 3' }
]);
</script>
</body>
</html>
2. Complete Minimal Example
import { Player, PlaylistManager } from 'vidply';
const player = new Player('#player');
const playlist = new PlaylistManager(player);
playlist.loadPlaylist([
{ src: 'a.mp3', type: 'audio/mp3', title: 'Track A' },
{ src: 'b.mp3', type: 'audio/mp3', title: 'Track B' }
]);
That's it!
Features
- Previous/Next Track Navigation - Skip between tracks with dedicated buttons
- Auto-Advance - Automatically play the next track when one finishes
- Loop Mode - Loop back to the first track after the last one
- Track Info Display - Shows current track number, title, and artist
- Visual Playlist Panel - Interactive list of all tracks with thumbnails with toggle button
- Active Track Highlighting - Visual indicator for the currently playing track
- Mixed Media Support - Combine audio and video files in a single playlist
- Lazy Loading - Media files only load when played (configurable)
- Enhanced Keyboard Navigation - Full keyboard support with arrow keys, Page Up/Down, Home/End
- Screen Reader Support - ARIA labels, live regions, and boundary announcements
- WCAG Compliant - Meets accessibility guidelines for keyboard navigation and screen readers
- Custom Tracks - Support for captions, chapters, and other text tracks per playlist item
Installation
The PlaylistManager is included in the VidPly package:
import { Player, PlaylistManager } from 'vidply';
Configuration Options
PlaylistManager Options
{
autoAdvance: true, // Automatically play next track when current ends
autoPlayFirst: true, // Auto-play first track on load (if false: load/select first track, but do not start playback)
loop: false, // Loop back to first track after last
showPanel: true, // Show visual playlist panel
panelPosition: 'below' // 'below' (default) or 'right' — desktop side-by-side layout from 75rem; mobile always stacks below
}
Track Object Structure
{
src: 'path/to/media.mp3', // Required: Media URL
type: 'audio/mp3', // Required: MIME type
title: 'Track Title', // Optional: Track title
artist: 'Artist Name', // Optional: Artist name
duration: 180, // Optional: Duration in seconds
poster: 'path/to/thumbnail.jpg', // Optional: Thumbnail image
downloadUrl: 'path/to/media.mp3', // Optional: File the download button offers for this track
downloadFormat: 'MP3', // Optional: Format for the download label (else inferred)
downloadFileSize: 7340032, // Optional: Size in bytes (else fetched via HEAD)
tracks: [ // Optional: Text tracks (captions, chapters)
{
src: 'captions.vtt',
kind: 'captions',
srclang: 'en',
label: 'English'
}
]
}
tracks also applies to YouTube and Vimeo items. YouTube's own captions are never shown, so sidecar WebVTT is the
only way to caption YouTube items; Vimeo's built-in text tracks appear in the CC menu as well. This needs the default
embedChrome: 'vidply' — with 'provider' the VidPly bar (and its captions menu) is hidden on embed tracks.
Previous/Next buttons are hidden when the playlist holds a single track.
Per-Track Downloads
The download button follows the playlist: set downloadUrl on the tracks that
may be downloaded and the button offers the selected track's file, relabels
itself with that file's format and size, and disappears on tracks that have no
downloadUrl. It needs downloadButton: true in the player options — the
per-track URL only decides which file is offered, not whether the button
exists at all.
const player = new Player('#audio', { downloadButton: true });
player.playlistManager.loadPlaylist([
{
src: 'episode-1.m3u8',
type: 'application/x-mpegURL',
title: 'Episode 1',
// Streams are not downloadable — offer the progressive file instead
downloadUrl: 'episode-1.mp3',
downloadFileSize: 7340032
},
{ src: 'episode-2.mp3', type: 'audio/mpeg', title: 'Episode 2' } // no download
]);
Playlists that set downloadUrl on no track at all keep the player-wide target
(downloadUrl option or data-vidply-download-url) for every track.
API Methods
loadPlaylist(items)
Load an array of track items into the playlist.
- If
autoPlayFirst: true(default): first track will start playing. - If
autoPlayFirst: false: first track will be selected and loaded (so poster/UI/features can initialize), but playback will not start.
playlist.loadPlaylist([
{ src: 'track1.mp3', type: 'audio/mp3', title: 'Track 1' },
{ src: 'track2.mp3', type: 'audio/mp3', title: 'Track 2' }
]);
Parameters:
items(Array): Array of track objects (see Track Object Structure above)
addItem(item)
Add a single track to the playlist.
playlist.addItem({
src: 'track3.mp3',
type: 'audio/mp3',
title: 'Track 3'
});
Parameters:
item(Object): Track object (see Track Object Structure above)
play(index)
Play a specific track by index (0-based).
playlist.play(2); // Play third track (0-based index)
Parameters:
index(Number): Zero-based index of the track to play
next()
Play the next track.
playlist.next();
previous()
Play the previous track.
playlist.previous();
hasNext()
Check if there's a next track.
if (playlist.hasNext()) {
playlist.next();
}
hasPrevious()
Check if there's a previous track.
if (playlist.hasPrevious()) {
playlist.previous();
}
getCurrentTrack()
Get the current track object.
const track = playlist.getCurrentTrack();
console.log(track.title);
togglePanel()
Toggle playlist panel visibility.
playlist.togglePanel();
Programmatic Control Examples
// Navigate
playlist.next(); // Go to next track
playlist.previous(); // Go to previous track
playlist.play(2); // Play track at index 2
// Check state
if (playlist.hasNext()) {
console.log('Has next track');
}
// Get current
const track = playlist.getCurrentTrack();
console.log(track.title);
// Listen to changes
player.on('playlisttrackchange', (e) => {
console.log('Now playing:', e.item.title);
});
Events
Listen to playlist events through the player:
// Track change event
player.on('playlisttrackchange', (e) => {
console.log('Now playing:', e.item.title);
console.log('Track index:', e.index);
});
Examples
Playlist with Captions
const tracks = [
{
src: 'song.mp3',
type: 'audio/mp3',
title: 'My Song',
artist: 'My Artist',
tracks: [
{ src: 'captions-en.vtt', kind: 'captions', srclang: 'en', label: 'English' },
{ src: 'captions-es.vtt', kind: 'captions', srclang: 'es', label: 'Español' }
]
}
];
playlist.loadPlaylist(tracks);
Dynamic Playlist
// Start with empty playlist
const playlist = new PlaylistManager(player);
// Add tracks dynamically
playlist.addItem({
src: 'new-song.mp3',
type: 'audio/mp3',
title: 'New Song'
});
// Or reload entire playlist
playlist.loadPlaylist(newTracksArray);
Video Playlist
// Create video player (note: mediaType: 'video')
const player = new Player('#video-player', {
mediaType: 'video'
});
const playlist = new PlaylistManager(player, {
autoAdvance: true,
showPanel: true
});
const videoTracks = [
{
src: 'video1.mp4',
type: 'video/mp4',
title: 'Episode 1',
poster: 'thumbnail1.jpg',
tracks: [
{ src: 'video1-captions.vtt', kind: 'captions', srclang: 'en', label: 'English' },
{ src: 'video1-chapters.vtt', kind: 'chapters', srclang: 'en', label: 'Chapters' }
]
},
{
src: 'video2.mp4',
type: 'video/mp4',
title: 'Episode 2',
poster: 'thumbnail2.jpg'
}
];
playlist.loadPlaylist(videoTracks);
Hide Playlist Panel
// Create without panel
const playlist = new PlaylistManager(player, {
showPanel: false
});
// Or toggle it
playlist.togglePanel();
Complete Example with All Features
import { Player, PlaylistManager } from 'vidply';
// Create player
const player = new Player('#audio-player', {
autoplay: false,
controls: true,
preload: 'metadata'
});
// Create playlist
const playlist = new PlaylistManager(player, {
autoAdvance: true,
loop: false,
showPanel: true
});
// Load tracks with captions and chapters
const tracks = [
{
src: 'media/song1.mp3',
type: 'audio/mp3',
title: 'Summer Vibes',
artist: 'The Acoustic Project',
duration: 245,
poster: 'media/album-art-1.jpg',
tracks: [
{
src: 'media/song1-captions-en.vtt',
kind: 'captions',
srclang: 'en',
label: 'English'
},
{
src: 'media/song1-chapters.vtt',
kind: 'chapters',
srclang: 'en',
label: 'Chapters'
}
]
},
{
src: 'media/song2.mp3',
type: 'audio/mp3',
title: 'Midnight Jazz',
artist: 'Blue Note Ensemble',
duration: 198,
poster: 'media/album-art-2.jpg'
}
];
playlist.loadPlaylist(tracks);
// Listen for track changes
player.on('playlisttrackchange', (e) => {
console.log(`Now playing: ${e.item.title} by ${e.item.artist}`);
// Optional: Update document title
document.title = `${e.item.title} - ${e.item.artist}`;
// Optional: Update media session API
if ('mediaSession' in navigator) {
navigator.mediaSession.metadata = new MediaMetadata({
title: e.item.title,
artist: e.item.artist,
artwork: [{ src: e.item.poster }]
});
}
});
// Optional: Add keyboard shortcuts
document.addEventListener('keydown', (e) => {
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') {
return; // Don't interfere with form inputs
}
switch(e.key) {
case 'ArrowLeft':
if (e.shiftKey) {
playlist.previous();
e.preventDefault();
}
break;
case 'ArrowRight':
if (e.shiftKey) {
playlist.next();
e.preventDefault();
}
break;
case 'p':
playlist.togglePanel();
break;
}
});
Fullscreen Behavior
In fullscreen mode, the playlist transforms into a horizontal, swipeable carousel (similar to YouTube):
- Auto-show/hide: Appears when paused or not started, hides when playing
- Horizontal layout: Cards displayed side-by-side with scroll-snap navigation
- Responsive: Desktop shows full cards (280px) with thumbnails, title, and artist
- Mobile: Compact view with thumbnail-only on portrait (<768px), text shown if no thumbnail
- Touch-friendly: Swipeable with smooth horizontal scrolling
- Position: Overlays above controls with semi-transparent backdrop
- Accessibility: Menus dynamically portal to container level to appear above playlist
All menus (chapters, quality, captions, etc.) remain accessible in fullscreen via dynamic DOM repositioning that maintains WCAG compliance.
UI Components
Track Info Display
Appears above the controls, showing:
- Track number (e.g., "3 / 10")
- Track title
- Artist name (if provided)
Playlist Panel
A scrollable list showing all tracks. Default placement is below the player; with panelPosition: 'right' (or data-playlist-panel-position="right") the player and controls sit in the left column and the playlist occupies the right column from 75rem viewport width upward. On narrower viewports the list always stacks below the player.
Each panel shows:
- Track thumbnails (if provided)
- Track numbers
- Track titles and artists
- Track duration (if provided)
- Active track indicator
Navigation Buttons
When a playlist is active:
- Previous button - Replaces rewind button
- Next button - Replaces forward button
- Buttons are disabled at playlist boundaries (unless loop is enabled)
Styling
Customize the playlist appearance with CSS:
/* Track info display */
.vidply-track-info {
background: your-gradient;
padding: 20px;
}
.vidply-track-title {
font-size: 18px;
color: #fff;
}
.vidply-track-artist {
color: rgba(255, 255, 255, 0.8);
}
/* Playlist panel */
.vidply-playlist-panel {
background: rgba(20, 20, 30, 0.95);
max-height: 400px;
}
.vidply-playlist-item-active {
background: rgba(59, 130, 246, 0.2);
border-left-color: #3b82f6;
}
/* Custom hover effects */
.vidply-playlist-item:hover {
background: rgba(255, 255, 255, 0.1);
}
/* Active playlist item */
.vidply-playlist-item-active {
background: linear-gradient(90deg, #667eea, #764ba2);
border-left-color: #fff;
}
Keyboard Navigation & Accessibility
Built-in Playlist Navigation
The playlist panel includes comprehensive keyboard navigation support:
- ↑ Up Arrow - Move to previous track in list
- ↓ Down Arrow - Move to next track in list
- Page Up - Jump up 5 tracks
- Page Down - Jump down 5 tracks
- Home - Jump to first track
- End - Jump to last track
- Enter / Space - Play the selected track
- Tab - Navigate to playlist panel and between tracks (roving tabindex pattern)
Playlist Toggle Button
When a PlaylistManager is active, a toggle button in the right control bar (with captions, settings, fullscreen) shows or hides the playlist panel. On narrow viewports the toggle may move into the overflow menu while transport controls stay visible.
The button supports keyboard access (Tab, Enter) and exposes aria-expanded, aria-pressed, and aria-controls for screen readers.
Screen Reader Support
The playlist includes extensive screen reader support:
- Live region announcements when navigating (e.g., "End of playlist. 5 of 5.")
- Boundary announcements (beginning/end of playlist)
- Track position announcements (e.g., "Track 3 of 10")
- Status announcements (currently playing, not playing)
- Descriptive labels for all interactive elements
Global Keyboard Shortcuts (Optional)
You can add global keyboard shortcuts for playlist navigation:
document.addEventListener('keydown', (e) => {
// Don't interfere with form inputs
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') {
return;
}
if (e.key === 'ArrowRight' && e.shiftKey) {
playlist.next();
e.preventDefault();
}
if (e.key === 'ArrowLeft' && e.shiftKey) {
playlist.previous();
e.preventDefault();
}
});
Accessibility Methods
// Toggle playlist panel visibility
playlist.togglePanel();
// Show playlist panel
playlist.showPanel();
// Hide playlist panel
playlist.hidePanel();
// Check if panel is visible
const isVisible = playlist.isPanelVisible;
Common Use Cases
Music Player
const musicPlayer = new PlaylistManager(player, {
autoAdvance: true,
loop: true,
showPanel: true
});
Perfect for:
- Album playback
- Artist discography
- Music compilations
Podcast Playlist
const podcast = new PlaylistManager(player, {
autoAdvance: false, // Manual navigation
loop: false,
showPanel: true
});
Perfect for:
- Podcast series
- Episode collections
- Interview playlists
Video Series (Netflix-style)
const series = new PlaylistManager(player, {
autoAdvance: true, // Binge watching
loop: false,
showPanel: true
});
Perfect for:
- TV series episodes
- Video courses
- Tutorial series
- Conference talks
Streaming Playlist (HLS & DASH)
playlist.loadPlaylist([
{ src: 'https://example.com/video1/manifest.mpd', title: 'DASH Stream' },
{ src: 'https://example.com/video2/master.m3u8', title: 'HLS Stream' },
{ src: 'fallback.mp4', type: 'video/mp4', title: 'MP4 Fallback' }
]);
VidPly auto-detects the renderer for each playlist item based on the source URL extension (.mpd for DASH, .m3u8 for HLS, etc.).
Mixed Media Playlist
const mixedPlaylist = new PlaylistManager(player, {
autoAdvance: true,
showPanel: true
});
mixedPlaylist.loadPlaylist([
{ src: 'intro.mp4', type: 'video/mp4', title: 'Introduction Video' },
{ src: 'episode1.mp3', type: 'audio/mp3', title: 'Episode 1 Audio' },
{ src: 'episode2.mp4', type: 'video/mp4', title: 'Episode 2 Video' },
{ src: 'bonus.mp3', type: 'audio/mp3', title: 'Bonus Content' }
]);
Perfect for:
- Mixed content courses (video lectures + audio supplements)
- Multimedia presentations
- Content with both video and audio episodes
- Educational content combining different media types
Audiobook Chapters
const audiobook = new PlaylistManager(player, {
autoAdvance: true,
loop: false,
showPanel: true
});
Perfect for:
- Chapter navigation
- Multi-part stories
- Audio learning content
Best Practices
- Avoid eager network loading: Use
deferLoad: true(and optionallypreload: 'none') to avoid starting downloads during init - Preload Metadata: Set
preload: 'metadata'to load track durations without downloading full files - Provide Thumbnails: Add poster images for better visual presentation
- Include Duration: Pre-calculate durations for better UX
- Use Consistent Naming: Keep track object properties consistent across your playlist
- Handle Loading States: Listen to
loadstartandcanplayevents for loading indicators - Accessibility: Ensure track titles and artists are descriptive for screen readers
Troubleshooting
Tracks Don't Auto-Advance
Make sure autoAdvance: true is set in the playlist options and the media element is emitting the ended event.
Previous/Next Buttons Not Showing
The buttons only appear when a PlaylistManager instance is attached to the player. Make sure you create the playlist manager before the controls are rendered, or call player.renderControls() after creating the playlist manager.
Playlist Panel Not Visible
Check that showPanel: true is set in the playlist options. The panel is inserted after the player element in the DOM.
Captions Don't Switch Between Tracks
Ensure the tracks array is included in each playlist item that has captions. The tracks will be reloaded when switching between playlist items.
Browser Support
The playlist feature works in all modern browsers that support:
- ES6 Modules
- HTML5 Media Elements
- WebVTT (for captions/chapters)
Tested in:
- Chrome/Edge 90+
- Firefox 88+
- Safari 14+
- Opera 76+
Demos
See the complete demos:
- Audio Playlist: demo/playlist-audio.html
- Video Playlist: demo/playlist-video.html
- Mixed Media Playlist: demo/playlist-mixed.html
Implementation Details
Files Added
src/features/PlaylistManager.js- Core playlist functionalitydemo/playlist-audio.html- Working audio playlist demo with 5 tracksdemo/playlist-video.html- Working video playlist demo with 3 videos
Files Modified
src/controls/ControlBar.js- Added previous/next buttonssrc/styles/vidply.css- Added playlist stylessrc/index.js- Exported PlaylistManagerdemo/demo.html- Added links to playlist demos
Built with Vanilla JavaScript