const audio = document.getElementById("audio-player"); const btnPlay = document.getElementById("btn-play"); const btnPrev = document.getElementById("btn-prev"); const btnNext = document.getElementById("btn-next"); const btnShuffle = document.getElementById("btn-shuffle"); const btnRepeat = document.getElementById("btn-repeat"); const progressBar = document.getElementById("progress-bar"); const currentTimeEl = document.getElementById("current-time"); const durationEl = document.getElementById("duration"); const volumeSlider = document.getElementById("volume"); const playlistEl = document.getElementById("playlist"); const trackTitle = document.getElementById("track-title"); const trackIndex = document.getElementById("track-index"); const playlistCount = document.getElementById("playlist-count"); let playlist = []; let currentIndex = 0; let shuffle = false; let repeat = false; let shuffleOrder = []; function formatTime(seconds) { if (isNaN(seconds)) return "0:00"; const m = Math.floor(seconds / 60); const s = Math.floor(seconds % 60).toString().padStart(2, "0"); return `${m}:${s}`; } function stripExtension(filename) { return filename.replace(/\.[^/.]+$/, ""); } function buildShuffleOrder() { shuffleOrder = [...Array(playlist.length).keys()]; for (let i = shuffleOrder.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [shuffleOrder[i], shuffleOrder[j]] = [shuffleOrder[j], shuffleOrder[i]]; } } function loadTrack(index, autoplay = false) { currentIndex = index; const filename = playlist[index]; audio.src = `/audio/${encodeURIComponent(filename)}`; trackTitle.textContent = stripExtension(filename); trackIndex.textContent = `Track ${index + 1} of ${playlist.length}`; document.querySelectorAll("#playlist li").forEach((li, i) => { li.classList.toggle("active", i === index); if (i === index) li.scrollIntoView({ block: "nearest" }); }); progressBar.value = 0; currentTimeEl.textContent = "0:00"; durationEl.textContent = "0:00"; if (autoplay) audio.play(); } function playNext() { if (playlist.length === 0) return; if (shuffle) { const pos = shuffleOrder.indexOf(currentIndex); const nextPos = (pos + 1) % shuffleOrder.length; loadTrack(shuffleOrder[nextPos], true); } else { loadTrack((currentIndex + 1) % playlist.length, true); } } function playPrev() { if (playlist.length === 0) return; if (audio.currentTime > 3) { audio.currentTime = 0; return; } if (shuffle) { const pos = shuffleOrder.indexOf(currentIndex); const prevPos = (pos - 1 + shuffleOrder.length) % shuffleOrder.length; loadTrack(shuffleOrder[prevPos], true); } else { loadTrack((currentIndex - 1 + playlist.length) % playlist.length, true); } } function renderPlaylist() { playlistEl.innerHTML = ""; playlistCount.textContent = `${playlist.length} track${playlist.length !== 1 ? "s" : ""}`; playlist.forEach((file, i) => { const li = document.createElement("li"); li.textContent = stripExtension(file); li.title = file; li.addEventListener("click", () => loadTrack(i, true)); playlistEl.appendChild(li); }); } async function loadPlaylist() { try { const res = await fetch("/api/playlist"); playlist = await res.json(); renderPlaylist(); if (playlist.length > 0) { buildShuffleOrder(); loadTrack(0); } else { trackTitle.textContent = "No audio files found"; trackIndex.textContent = "Drop .mp3 / .ogg / .wav files into the audio/ folder"; } } catch (e) { trackTitle.textContent = "Error loading playlist"; } } // Controls btnPlay.addEventListener("click", () => { if (audio.paused) { if (!audio.src) loadTrack(0, true); else audio.play(); } else { audio.pause(); } }); btnNext.addEventListener("click", playNext); btnPrev.addEventListener("click", playPrev); btnShuffle.addEventListener("click", () => { shuffle = !shuffle; btnShuffle.classList.toggle("active", shuffle); if (shuffle) buildShuffleOrder(); }); btnRepeat.addEventListener("click", () => { repeat = !repeat; btnRepeat.classList.toggle("active", repeat); }); // Audio events audio.addEventListener("play", () => { btnPlay.textContent = "⏸"; }); audio.addEventListener("pause", () => { btnPlay.textContent = "▶"; }); audio.addEventListener("timeupdate", () => { if (audio.duration) { progressBar.value = (audio.currentTime / audio.duration) * 100; currentTimeEl.textContent = formatTime(audio.currentTime); } }); audio.addEventListener("loadedmetadata", () => { durationEl.textContent = formatTime(audio.duration); }); audio.addEventListener("ended", () => { if (repeat) { audio.currentTime = 0; audio.play(); } else { playNext(); } }); progressBar.addEventListener("input", () => { if (audio.duration) { audio.currentTime = (progressBar.value / 100) * audio.duration; } }); volumeSlider.addEventListener("input", () => { audio.volume = volumeSlider.value / 100; }); // Set initial volume audio.volume = volumeSlider.value / 100; // Keyboard shortcuts document.addEventListener("keydown", (e) => { if (e.target.tagName === "INPUT") return; switch (e.key) { case " ": e.preventDefault(); btnPlay.click(); break; case "ArrowRight": playNext(); break; case "ArrowLeft": playPrev(); break; case "ArrowUp": volumeSlider.value = Math.min(100, +volumeSlider.value + 5); audio.volume = volumeSlider.value / 100; break; case "ArrowDown": volumeSlider.value = Math.max(0, +volumeSlider.value - 5); audio.volume = volumeSlider.value / 100; break; } }); loadPlaylist();