Building a Full-Featured Web Music Player: Dev Guide
A step-by-step tutorial covering audio playback, LRC lyric sync, drag-and-drop playlists, floating lyrics, responsive design, and i18n for a complete web music player.
1. Project Overview
This tutorial walks through building a complete web-based music player with 10 original AI-generated songs. The player supports playback controls, synced lyrics, playlists, favorites, and a floating desktop lyrics window — all in pure HTML/CSS/JavaScript with no frameworks.
Core Features Implemented
- Play / Pause / Previous / Next controls
- Three play modes: sequential, shuffle, repeat-one
- LRC-format synchronized lyrics with 1-line / full view toggle
- Drag-and-drop playlist reordering
- Favorites and play-count tracking via Cloudflare KV
- Floating desktop lyrics window with transparency, color, and size controls
- Progress bar drag-to-seek (mouse + touch)
- Fully responsive (desktop / tablet / mobile)
- Bilingual (English / Chinese) with dynamic i18n
2. Project Structure
aigomoon/
├── music.html # Main player page
├── css/style.css # All styles (responsive breakpoints)
├── js/
│ ├── main.js # Shared i18n, header/footer loader
│ └── music.js # Player logic (~900 lines)
├── assets/
│ ├── music/ # 10 MP3 files
│ ├── lrc/ # 10 LRC lyric files
│ └── images/music-covers/ # 10 album covers
└── functions/
├── api/stats.js # GET play/fav counts
└── api/click.js # POST increment plays
3. Core Player Architecture
The player uses a single global Audio object and a play-queue array. Key state variables:
var music_playQueue = []; // Song IDs in play order
var music_currentIndex = 0; // Current position in queue
var music_userPlaylist = []; // User's saved playlist (localStorage)
var music_favorites = []; // Favorite song IDs (localStorage)
var music_lrcLines = []; // Parsed LRC [{time, text}]
Important design decision: The play queue always equals the user playlist. When a user clicks any song, it's auto-appended to both the queue and the saved playlist. This ensures Next/Previous only navigate within the user's curated list.
4. LRC Lyrics Synchronization
Parsing LRC files is straightforward with regex:
fetch(url).then(r => r.text()).then(text => {
var lines = text.split('\n');
for (var i = 0; i < lines.length; i++) {
var m = lines[i].match(/\[(\d{2}):(\d{2})\.(\d{2})\](.*)/);
if (m) {
music_lrcLines.push({
time: parseInt(m[1])*60 + parseInt(m[2]) + parseInt(m[3])/100,
text: m[4].trim()
});
}
}
});
On each timeupdate event, find the current line by scanning backward from the end:
audio.addEventListener('timeupdate', function() {
for (var i = music_lrcLines.length - 1; i >= 0; i--) {
if (this.currentTime >= music_lrcLines[i].time) {
// Highlight this line, scroll into view
break;
}
}
});
5. Drag-and-Drop Playlist Reordering
Native HTML5 Drag & Drop API handles reordering. Each row gets draggable="true" in playlist mode:
row.addEventListener('drop', function(e) {
e.preventDefault();
var srcId = dragSrc.getAttribute('data-id');
var targetId = row.getAttribute('data-id');
// Remove source, insert before target
music_userPlaylist.splice(srcIdx, 1);
music_userPlaylist.splice(newTgtIdx, 0, srcId);
localStorage.setItem('music_playlist', JSON.stringify(music_userPlaylist));
music_renderSongList();
});
Gotcha: A justDragged flag prevents the post-drag click event from triggering song playback.
6. Floating Desktop Lyrics Window
A draggable overlay window shows 1-2 lines of lyrics. Features include:
- Drag to move (mouse + touch)
- Pin position toggle
- 6 color options for current lyric
- Font size adjustment (A+ / A-)
- Transparent background mode with auto-hiding controls (3s timeout, reappear on hover/touch)
Transparent mode CSS uses pointer-events: none when controls are hidden, so clicks pass through to underlying content.
7. Responsive Design Lessons
The trickiest issue was column overflow in the song list. On narrow desktop windows (~1080px), the left list panel was only ~430px wide but the grid had 7 fixed columns totaling 420px — leaving the title column at near-zero width, so song names disappeared while genres remained visible.
Solution: Raise the single-column breakpoint from 900px to 1200px. Below 1200px, switch to stacked layout (list on top, player below) and hide the Artist/Plays columns to give the title column room.
8. i18n Approach
A shared I18N dictionary in main.js holds all UI strings. Elements use data-i18n="key" attributes; applyLang() updates textContent on language switch. Dynamic strings (toasts, loading messages) call music_t('key') at runtime. The music page adds 30+ keys for tabs, buttons, headers, and tooltips.
9. Final Checklist
| Feature | Implementation |
|---|---|
| Audio playback | Native Audio API, preload none |
| Lyrics sync | LRC regex parse + timeupdate scan |
| Playlist order | Drag & drop, localStorage persist |
| Play counts | Cloudflare KV via Pages Functions |
| Floating lyrics | Fixed overlay, transparent mode |
| Responsive | 1200px breakpoint, column hiding |
| i18n | data-i18n attributes + runtime t() |
With these patterns, you can build a production-quality music player in under 1000 lines of vanilla JavaScript — no build step, no frameworks, just deploy to any static host.