function formatTime(seconds) { const m = Math.floor(seconds / 60); const s = Math.floor(seconds % 60).toString().padStart(2, '0'); return `${m}:${s}`; } const removeFromQueue = async (trackId) => { await api(`/api/queue/${trackId}`, { method: "DELETE" }); updateQueue(); // Refresh queue after deletion }; function updateProgress(position, duration) { const progressBar = document.getElementById("progressBar"); const elapsedTime = document.getElementById("elapsedTime"); const totalTime = document.getElementById("totalTime"); progressBar.max = duration; progressBar.value = position; elapsedTime.textContent = formatTime(position); totalTime.textContent = formatTime(duration); } const api = (endpoint, options = {}) => fetch(endpoint, options).then(res => res.json()).catch(console.error); const setVolume = async (val) => { await api(`/api/player/volume?volume=${val}`, { method: "PUT" }); document.getElementById("volumeValue").textContent = val; }; const addToQueue = async () => { const url = document.getElementById("trackUrl").value; await api(`/api/queue?url=${encodeURIComponent(url)}`, { method: "POST" }); updateQueue(); }; const play = async () => { await api("/api/player/play", { method: "POST" });}; const stop = async () => { await api("/api/player/stop", { method: "POST" });}; const skip = async () => { await api("/api/player/skip", { method: "POST" });}; // WebSocket connections let playerSocket, queueSocket; function connectWebSockets() { const proto = location.protocol === "https:" ? "wss" : "ws"; const base = `${proto}://${location.host}/api`; playerSocket = new WebSocket(`${base}/player`); queueSocket = new WebSocket(`${base}/queue`); playerSocket.onopen = () => playerSocket.send("ping"); queueSocket.onopen = () => queueSocket.send("ping"); playerSocket.onmessage = (event) => { const state = JSON.parse(event.data); const { playback_state, track, position, volume } = state; document.getElementById("trackArtist").textContent = track?.artist || "-"; document.getElementById("trackTitle").textContent = track?.title || "-"; document.getElementById("playbackState").textContent = playback_state; document.getElementById("volumeSlider").value = volume; document.getElementById("volumeValue").textContent = volume; if (track) { updateProgress(position, track.duration); } }; queueSocket.onmessage = (event) => { const queue = JSON.parse(event.data); const queueBody = document.getElementById("queueBody"); queueBody.innerHTML = ""; (queue.items || queue).forEach((track, index) => { const row = document.createElement("tr"); row.innerHTML = `