# VidPly Playlist Feature
The `PlaylistManager` allows you to create audio and video playlists with automatic track switching, navigation controls, and a visual playlist panel.
## Quick Start
### 1. Basic Audio Playlist (30 seconds)
```html
```
### 2. Complete Minimal Example
```javascript
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:
```javascript
import { Player, PlaylistManager } from 'vidply';
```
## Configuration Options
### PlaylistManager Options
```javascript
{
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
}
```
### Track Object Structure
```javascript
{
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
tracks: [ // Optional: Text tracks (captions, chapters)
{
src: 'captions.vtt',
kind: 'captions',
srclang: 'en',
label: 'English'
}
]
}
```
## 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.
```javascript
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.
```javascript
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).
```javascript
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.
```javascript
playlist.next();
```
### previous()
Play the previous track.
```javascript
playlist.previous();
```
### hasNext()
Check if there's a next track.
```javascript
if (playlist.hasNext()) {
playlist.next();
}
```
### hasPrevious()
Check if there's a previous track.
```javascript
if (playlist.hasPrevious()) {
playlist.previous();
}
```
### getCurrentTrack()
Get the current track object.
```javascript
const track = playlist.getCurrentTrack();
console.log(track.title);
```
### togglePanel()
Toggle playlist panel visibility.
```javascript
playlist.togglePanel();
```
## Programmatic Control Examples
```javascript
// 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:
```javascript
// Track change event
player.on('playlisttrackchange', (e) => {
console.log('Now playing:', e.item.title);
console.log('Track index:', e.index);
});
```
## Examples
### Playlist with Captions
```javascript
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
```javascript
// 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
```javascript
// 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
```javascript
// Create without panel
const playlist = new PlaylistManager(player, {
showPanel: false
});
// Or toggle it
playlist.togglePanel();
```
## Complete Example with All Features
```javascript
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 below the player showing:
- 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:
```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
A playlist toggle button is automatically added to the control bar when a PlaylistManager is active. This button allows users to:
- Toggle the playlist panel visibility
- Access the playlist with keyboard navigation (Tab to button, Enter to toggle)
- Proper ARIA attributes for screen readers (`aria-expanded`, `aria-pressed`, `aria-controls`)
### 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:
```javascript
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
```javascript
// 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
```javascript
const musicPlayer = new PlaylistManager(player, {
autoAdvance: true,
loop: true,
showPanel: true
});
```
Perfect for:
- Album playback
- Artist discography
- Music compilations
### Podcast Playlist
```javascript
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)
```javascript
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)
```javascript
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
```javascript
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
```javascript
const audiobook = new PlaylistManager(player, {
autoAdvance: true,
loop: false,
showPanel: true
});
```
Perfect for:
- Chapter navigation
- Multi-part stories
- Audio learning content
## Best Practices
1. **Avoid eager network loading**: Use `deferLoad: true` (and optionally `preload: 'none'`) to avoid starting downloads during init
2. **Preload Metadata**: Set `preload: 'metadata'` to load track durations without downloading full files
3. **Provide Thumbnails**: Add poster images for better visual presentation
4. **Include Duration**: Pre-calculate durations for better UX
5. **Use Consistent Naming**: Keep track object properties consistent across your playlist
6. **Handle Loading States**: Listen to `loadstart` and `canplay` events for loading indicators
7. **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](../demo/playlist-audio.html)
- **Video Playlist**: [demo/playlist-video.html](../demo/playlist-video.html)
- **Mixed Media Playlist**: [demo/playlist-mixed.html](../demo/playlist-mixed.html)
## Implementation Details
### Files Added
- `src/features/PlaylistManager.js` - Core playlist functionality
- `demo/playlist-audio.html` - Working audio playlist demo with 5 tracks
- `demo/playlist-video.html` - Working video playlist demo with 3 videos
### Files Modified
- `src/controls/ControlBar.js` - Added previous/next buttons
- `src/styles/vidply.css` - Added playlist styles
- `src/index.js` - Exported PlaylistManager
- `demo/demo.html` - Added links to playlist demos
---
**Built with Vanilla JavaScript**