# VidPly 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**