Music Player App with HTML, CSS & JavaScript
20 DAYS 20 PROJECT CHALLENGE
Day #20
Project Overview
The Music Player App is a simple and interactive mini player built using HTML, CSS, and JavaScript. It uses the HTML5 Audio API to play music, update the progress bar in real time, and manage playback states. User can play/pause the selected track, skip the audio forward or backward, adjust the volume, and choose the desired song from the playlist.
It also provides the cover, title, author, progress bar, and time of the currently played track. Additionally, it allows the user to click the progress bar to change the current playback position and enables shuffle and repeat modes. This project includes an array of songs that can be easily updated or expanded and it demonstrates how JavaScript can be used to operate with HTML audio elements, arrays, manage the state of the application and its components, and update the DOM.
Key Features
- Play, Pause Music: Users can start playing the selected track and pause it whenever needed. The Play button also changes according to the current playback state.
- Next, Previous Buttons: You can go to the next or previous track by clicking the Next and Previous buttons. You do not need to reload or refresh the page.
- Playlist Selection: A playlist displays the available tracks, including their names and artist information. Users can click directly on a track to load and play that particular song.
- Active Track Highlight: The currently selected song is visually highlighted in the playlist. This makes it easier for users to identify which track is currently loaded in the player.
- Progress Bar: The progress bar updates while the music plays and visually shows how much of the current track has been completed.
- Seek Through a Song: Users can interact with the progress bar to move to a different position within the track. This makes it possible to skip forward or return to an earlier part of the song.
- Current Time And Duration: This project also shows the current time of the playing song and the total duration will also appear in the music player.
- Automatic Next Track: When a song finishes, the player can move to the next available track. This allows the playlist to continue without requiring the user to select every song manually.
- Shuffle Mode: Shuffle mode allows the application to select another track randomly from the playlist. This creates a less predictable listening experience.
- Repeat Mode: Using this mode you can decide to repeat a track or the entire playlist according to your preferences.
- Volume Control: A volume slider allows users to increase or decrease the sound level directly from the music player interface.
- Mute Control: The player includes a mute option that allows users to quickly turn off the audio without changing the selected volume permanently.
- Keyboard Shortcuts: You can use keyboard shortcuts to operate your music player. For instance, you can use the Space key to toggle between Pause and Play and use the left and right keys to change tracks.
- Responsive Design: The layout adjusts to different screen sizes so the music player and playlist remain accessible on desktops, tablets, and mobile devices.
- Easy customization: You can customize the audio player according to your needs. Firstly, you can change the playlist and add your favorite tracks. Secondly, you can edit the URL, song name, artist, and album cover.
What You'll Learn
- Understand how to work with the HTML
<audio>element. - Learn how JavaScript controls audio playback.
- Practice using
play()andpause()methods. - Understand how to store songs using JavaScript arrays and objects.
- Learn how to update track information dynamically.
- Practice handling click events on music controls.
- Understand how to create and update a playback progress bar.
- Learn how the
timeupdateevent provides real-time playback information. - Practice calculating and displaying current time and total duration.
- Understand how to create a dynamic playlist using JavaScript.
- Learn how to manage application state using variables.
- Practice generating random tracks for shuffle functionality.
- Understand how volume controls work with the Audio API.
- Learn how keyboard events can improve user interaction.
HTML Code
HTML creates the structure of the Music Player App. First, the main .card container holds the application heading and the complete music player interface. Inside the player section, the .now-playing area displays information about the currently selected song, <img> element with the cover ID displays the album cover, while the trackTitle and trackArtist elements show the name of the current song and artist. Next, the progressContainer and progress elements create the visual playback progress bar, currentTime and duration elements display the current playback time and the total length of the song. Control section contains buttons for Previous, Play or Pause, Next, Shuffle, Repeat, and Mute actions. A range input with the volume ID allows users to adjust the audio volume.
Playlist section of this project contains the playlistList element, where JavaScript dynamically adds the available tracks. Finally, the <audio> element provides the actual audio functionality, while script.js is loaded near the bottom of the page so JavaScript can access the required HTML elements and add interactive behavior.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Day 20 — Music Player App</title>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<main class="card" role="main" aria-labelledby="title">
<header class="top">
<div>
<h1 id="title">Day 20: Music Player App</h1>
<p class="lead">Mini music player demonstrating the Audio API, event listeners and state management.</p>
</div>
</header>
<section class="player">
<div class="now-playing">
<img id="cover" class="cover" src="https://picsum.photos/300?random=10" alt="Album art" />
<div class="meta">
<div id="trackTitle" class="title">Track Title</div>
<div id="trackArtist" class="artist">Artist</div>
<div class="progress-wrap" aria-label="Track progress">
<div id="progressContainer" class="progress-container" role="slider" tabindex="0" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0">
<div id="progress" class="progress"></div>
</div>
<div class="time-row">
<span id="currentTime">0:00</span>
<span id="duration">0:00</span>
</div>
</div>
<div class="controls">
<button id="prevBtn" class="btn" title="Previous (Arrow Left)">⏮</button>
<button id="playPauseBtn" class="btn primary" title="Play / Pause (Space)">▶</button>
<button id="nextBtn" class="btn" title="Next (Arrow Right)">⏭</button>
<button id="shuffleBtn" class="btn" title="Shuffle">🔀</button>
<button id="repeatBtn" class="btn" title="Repeat">🔁</button>
<div class="vol">
<button id="muteBtn" class="btn" title="Mute">🔈</button>
<input id="volume" type="range" min="0" max="1" step="0.01" value="0.8" aria-label="Volume" />
</div>
</div>
</div>
</div>
<aside class="playlist" aria-label="Playlist">
<h3>Playlist</h3>
<ol id="playlistList" class="playlist-list"></ol>
</aside>
</section>
<audio id="audio" preload="metadata"></audio>
<details style="margin-top:12px">
<summary>How it works (short)</summary>
<p class="small">JS controls a single <audio> element. Controls call play/pause/seek functions. The `timeupdate` event updates the progress bar and time labels. Playlist is an array of track objects (src, title, artist, cover).</p>
</details>
</main>
<script src="script.js"></script>
</body>
</html>
CSS Code
CSS controls the appearance and layout of the Music Player App. First, the .card class styles the main application container by adding padding, rounded corners, borders, and shadows. Next, the .player and .now-playing classes use Flexbox to arrange the music information and playlist in an organized layout, .cover class styles the album artwork and the .meta section organizes the song title, artist information, progress bar, and controls, .progress-container creates the background track for the playback bar, and .progress represents the completed portion of the song.
The .controls class arranges the music buttons, while .btn and .btn.primary provide different styles for regular and primary controls. The playlist uses .playlist-list and its list items to display the available tracks, and the .active class highlights the currently selected song. .
:root {
--bg: #071026;
--card: #0b1220;
--accent: #06b6d4;
--accent2: #7c3aed;
--muted: #9aa4b2;
--white: #e6eef6;
font-family: Inter, system-ui, -apple-system, 'Segoe UI', Roboto, Arial;
}
* {
box-sizing: border-box
}
html,
body {
height: 100%;
margin: 0;
background: #002252;
color: var(--white)
}
body {
display: flex;
align-items: center;
justify-content: center;
padding: 28px
}
.card {
width: min(980px, 96%);
background: linear-gradient(180deg, rgba(255, 255, 255, 0.02), rgba(255, 255, 255, 0.01));
border-radius: 12px;
padding: 18px;
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.6);
border: 1px solid rgba(255, 255, 255, 0.03);
}
.top {
display: flex;
gap: 12px;
align-items: center
}
.logo {
width: 60px;
height: 60px;
border-radius: 10px;
object-fit: cover
}
h1 {
margin: 0;
font-size: 18px
}
.lead {
margin: 4px 0 12px;
color: var(--muted);
font-size: 14px
}
.player {
display: flex;
gap: 18px;
align-items: flex-start;
margin-top: 12px
}
.now-playing {
display: flex;
gap: 14px;
align-items: flex-start;
flex: 1
}
.cover {
width: 160px;
height: 160px;
border-radius: 10px;
object-fit: cover;
border: 1px solid rgba(255, 255, 255, 0.04)
}
.meta {
flex: 1;
display: flex;
flex-direction: column;
gap: 12px
}
.title {
font-size: 18px;
font-weight: 700
}
.artist {
color: var(--muted);
font-size: 14px
}
.progress-wrap {
display: flex;
flex-direction: column;
gap: 8px
}
.progress-container {
height: 10px;
background: rgba(255, 255, 255, 0.04);
border-radius: 999px;
position: relative;
cursor: pointer
}
.progress {
height: 100%;
width: 0%;
background: linear-gradient(90deg, var(--accent2), var(--accent));
border-radius: 999px;
transition: width 120ms linear
}
.time-row {
display: flex;
justify-content: space-between;
color: var(--muted);
font-family: ui-monospace, monospace;
font-size: 13px
}
.controls {
display: flex;
gap: 8px;
align-items: center;
flex-wrap: wrap
}
.btn {
padding: 8px 10px;
border-radius: 8px;
border: 0;
background: transparent;
color: var(--white);
cursor: pointer;
font-size: 18px
}
.btn.primary {
background: linear-gradient(90deg, var(--accent2), var(--accent));
padding: 10px 14px;
border-radius: 10px
}
.vol {
display: flex;
gap: 8px;
align-items: center
}
input[type="range"] {
width: 110px
}
.playlist {
width: 300px;
border-left: 1px solid rgba(255, 255, 255, 0.03);
padding-left: 14px
}
.playlist h3 {
margin: 0 0 8px 0
}
.playlist-list {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
gap: 8px
}
.playlist-list li {
padding: 8px;
border-radius: 8px;
background: rgba(255, 255, 255, 0.02);
cursor: pointer;
display: flex;
gap: 8px;
align-items: center;
justify-content: space-between;
border: 1px solid rgba(255, 255, 255, 0.03)
}
.playlist-list li.active {
outline: 3px solid rgba(124, 58, 237, 0.08);
transform: translateY(-2px)
}
.playlist-list li .meta {
flex: 1;
display: flex;
flex-direction: column;
margin-left: 8px
}
.playlist-list li .meta .name {
font-weight: 600
}
.playlist-list li .meta .artist {
font-size: 12px;
color: var(--muted)
}
.small {
font-size: 13px;
color: var(--muted)
}
@media (max-width:880px) {
.player {
flex-direction: column
}
.playlist {
width: 100%;
border-left: 0;
padding-left: 0
}
.cover {
width: 120px;
height: 120px
}
} Javascript Code
JavaScript provides the main functionality of the Music Player App. First, the TRACKS array stores information about each song, including the audio source, title, artist name, and cover image. JavaScript then selects the audio element, playback controls, progress bar, volume control, and playlist elements from the HTML. The init() function prepares the application by creating the playlist, loading the first track, attaching event listeners, setting the initial volume, and updating the Play button. Next, buildPlaylistUI() creates the playlist items dynamically from the TRACKS array, loadTrack() function changes the current song and updates the cover image, title, artist name, progress information, and active playlist item. Also, the play(), pause(), and togglePlay() functions manage the playback state and update the Play or Pause button.
When shuffle mode is active, JavaScript can select another track randomly. Repeat settings control what happens when a song reaches the end. It also listens for audio events to update the progress bar and time display while the song plays. Event listeners connect the buttons, volume slider, playlist, progress controls, and keyboard shortcuts to their corresponding actions, creating a complete interactive music player.
// Day 20 — Music Player App
// Concepts: Audio element control, events, progress seek, playlist, shuffle/repeat
// Playlist — free royalty-free MP3 URLs (Bensound) + sample covers from picsum
const TRACKS = [
{
src: "https://www.bensound.com/bensound-music/bensound-acousticbreeze.mp3",
title: "Acoustic Breeze",
artist: "Bensound",
cover: "https://picsum.photos/seed/acoustic/400/400"
},
{
src: "https://www.bensound.com/bensound-music/bensound-creativeminds.mp3",
title: "Creative Minds",
artist: "Bensound",
cover: "https://picsum.photos/seed/creative/400/400"
},
{
src: "https://www.bensound.com/bensound-music/bensound-betterdays.mp3",
title: "Better Days",
artist: "Bensound",
cover: "https://picsum.photos/seed/better/400/400"
},
{
src: "https://www.bensound.com/bensound-music/bensound-goinghigher.mp3",
title: "Going Higher",
artist: "Bensound",
cover: "https://picsum.photos/seed/higher/400/400"
},
{
src: "https://www.bensound.com/bensound-music/bensound-sunny.mp3",
title: "Sunny",
artist: "Bensound",
cover: "https://picsum.photos/seed/sunny/400/400"
}
];
// DOM refs
const audio = document.getElementById('audio');
const coverEl = document.getElementById('cover');
const trackTitleEl = document.getElementById('trackTitle');
const trackArtistEl = document.getElementById('trackArtist');
const playPauseBtn = document.getElementById('playPauseBtn');
const prevBtn = document.getElementById('prevBtn');
const nextBtn = document.getElementById('nextBtn');
const progressContainer = document.getElementById('progressContainer');
const progressEl = document.getElementById('progress');
const currentTimeEl = document.getElementById('currentTime');
const durationEl = document.getElementById('duration');
const volumeInput = document.getElementById('volume');
const muteBtn = document.getElementById('muteBtn');
const playlistList = document.getElementById('playlistList');
const shuffleBtn = document.getElementById('shuffleBtn');
const repeatBtn = document.getElementById('repeatBtn');
// Player state
let currentIndex = 0;
let isPlaying = false;
let isShuffle = false;
let repeatMode = 'off'; // off | one | all
let lastVolume = Number(volumeInput.value) || 0.8;
// Initialize playlist UI and audio
function init() {
buildPlaylistUI();
loadTrack(currentIndex);
attachEvents();
audio.volume = Number(volumeInput.value);
updatePlayButton();
}
// Build playlist list items
function buildPlaylistUI() {
playlistList.innerHTML = '';
TRACKS.forEach((t, i) => {
const li = document.createElement('li');
li.dataset.index = i;
li.innerHTML = `<div class="meta"><strong class="name">${t.title}</strong><div class="artist">${t.artist}</div></div>
<div class="time muted">—:—</div>`;
if (i === currentIndex) li.classList.add('active');
li.addEventListener('click', () => {
loadTrack(i);
play();
});
playlistList.appendChild(li);
});
}
// Load a track by index (does not auto-play)
function loadTrack(index) {
if (index < 0 || index >= TRACKS.length) return;
currentIndex = index;
const track = TRACKS[index];
audio.src = track.src;
coverEl.src = track.cover || 'https://picsum.photos/400';
trackTitleEl.textContent = track.title;
trackArtistEl.textContent = track.artist;
Array.from(playlistList.children).forEach(li => li.classList.toggle('active', Number(li.dataset.index) === index));
progressEl.style.width = '0%';
currentTimeEl.textContent = '0:00';
durationEl.textContent = '0:00';
audio.load();
}
// Play / Pause helpers
function play() {
audio.play().then(() => {
isPlaying = true;
updatePlayButton();
}).catch(err => {
console.error('Playback failed:', err);
});
}
function pause() {
audio.pause();
isPlaying = false;
updatePlayButton();
}
function togglePlay() {
if (isPlaying) pause(); else play();
}
function updatePlayButton() {
playPauseBtn.textContent = isPlaying ? '⏸' : '▶';
}
// Next / Prev
function nextTrack() {
if (isShuffle) {
let next;
if (TRACKS.length === 1) next = 0;
else {
do { next = Math.floor(Math.random() * TRACKS.length); } while (next === currentIndex && TRACKS.length > 1);
}
loadTrack(next);
play();
return;
}
if (currentIndex < TRACKS.length - 1) {
loadTrack(currentIndex + 1);
play();
} else {
if (repeatMode === 'all') {
loadTrack(0);
play();
} else {
pause();
audio.currentTime = 0;
}
}
}
function prevTrack() {
if (audio.currentTime > 5) {
audio.currentTime = 0;
return;
}
if (currentIndex > 0) {
loadTrack(currentIndex - 1);
play();
} else {
audio.currentTime = 0;
}
}
// Format seconds to mm:ss
function fmtTime(sec) {
if (!isFinite(sec) || sec === 0) return '0:00';
const s = Math.floor(sec % 60);
const m = Math.floor(sec / 60);
return `${m}:${String(s).padStart(2, '0')}`;
}
// Attach events
function attachEvents() {
audio.addEventListener('loadedmetadata', () => {
durationEl.textContent = fmtTime(audio.duration);
const li = playlistList.querySelector(`li[data-index="${currentIndex}"]`);
if (li) {
const t = li.querySelector('.time');
if (t) t.textContent = fmtTime(audio.duration);
}
});
audio.addEventListener('timeupdate', () => {
const pct = (audio.currentTime / audio.duration) * 100 || 0;
progressEl.style.width = `${pct}%`;
currentTimeEl.textContent = fmtTime(audio.currentTime);
});
audio.addEventListener('ended', () => {
if (repeatMode === 'one') {
audio.currentTime = 0;
play();
} else {
nextTrack();
}
});
playPauseBtn.addEventListener('click', togglePlay);
prevBtn.addEventListener('click', prevTrack);
nextBtn.addEventListener('click', nextTrack);
// progress seek
let isSeeking = false;
function seekFromEvent(e) {
const rect = progressContainer.getBoundingClientRect();
const x = (e.touches ? e.touches[0].clientX : e.clientX) - rect.left;
const pct = Math.max(0, Math.min(1, x / rect.width));
audio.currentTime = pct * (audio.duration || 0);
}
progressContainer.addEventListener('click', (e) => seekFromEvent(e));
progressContainer.addEventListener('pointerdown', (e) => {
isSeeking = true;
progressContainer.setPointerCapture(e.pointerId);
seekFromEvent(e);
});
progressContainer.addEventListener('pointermove', (e) => {
if (!isSeeking) return;
seekFromEvent(e);
});
progressContainer.addEventListener('pointerup', (e) => {
isSeeking = false;
try { progressContainer.releasePointerCapture(e.pointerId); } catch(_) {}
});
progressContainer.addEventListener('pointercancel', () => isSeeking = false);
// volume & mute
volumeInput.addEventListener('input', () => {
audio.volume = Number(volumeInput.value);
if (audio.volume > 0) {
muteBtn.textContent = '🔈';
lastVolume = audio.volume;
} else {
muteBtn.textContent = '🔇';
}
});
muteBtn.addEventListener('click', () => {
if (audio.volume > 0) {
lastVolume = audio.volume;
audio.volume = 0;
volumeInput.value = 0;
muteBtn.textContent = '🔇';
} else {
audio.volume = lastVolume || 0.8;
volumeInput.value = audio.volume;
muteBtn.textContent = '🔈';
}
});
// shuffle & repeat
shuffleBtn.addEventListener('click', () => {
isShuffle = !isShuffle;
shuffleBtn.style.opacity = isShuffle ? '1' : '0.6';
});
repeatBtn.addEventListener('click', () => {
if (repeatMode === 'off') repeatMode = 'one';
else if (repeatMode === 'one') repeatMode = 'all';
else repeatMode = 'off';
repeatBtn.textContent = repeatMode === 'off' ? '🔁' : (repeatMode === 'one' ? '🔂' : '🔁');
repeatBtn.style.opacity = repeatMode === 'off' ? '0.7' : '1';
});
// keyboard shortcuts
document.addEventListener('keydown', (e) => {
if (e.code === 'Space') {
e.preventDefault();
togglePlay();
} else if (e.code === 'ArrowRight') {
nextTrack();
} else if (e.code === 'ArrowLeft') {
prevTrack();
}
});
audio.addEventListener('error', (e) => {
console.error('Audio error', e);
nextTrack();
});
}
// initialize
init();
Related Projects
Day 18 : Text-to-Speech Converter
Converts entered text into speech.
Concepts: Web Speech API.
Day 14 : Image Gallery with Modal
Displays images in a grid with a pop-up modal view.
Concepts: DOM traversal, event bubbling.
Day 15 : Light/Dark Mode Toggle
Switch between light and dark themes with one click (and remember the choice).
Concepts: CSS variables, LocalStorage.