// DOM Elements const statusIndicator = document.getElementById('status-indicator'); const statusDot = document.querySelector('.status-dot'); const statusText = document.getElementById('status-text'); const nowPlaying = document.getElementById('now-playing'); const notPlaying = document.getElementById('not-playing'); const lastUpdate = document.getElementById('last-update'); const songHistory = document.getElementById('song-history'); const historyList = document.getElementById('history-list'); // Song info elements const coverArt = document.getElementById('cover-art'); const songTitle = document.getElementById('song-title'); const songArtist = document.getElementById('song-artist'); const songAlbum = document.getElementById('song-album'); const songDuration = document.getElementById('song-duration'); const songGenre = document.getElementById('song-genre'); const songYear = document.getElementById('song-year'); // Progress bar elements const progressBar = document.getElementById('progress-bar'); const currentTime = document.getElementById('current-time'); const totalTime = document.getElementById('total-time'); // Base64 encoded default cover art (simple music note icon) const DEFAULT_COVER_ART = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAYAAACtWK6eAAAACXBIWXMAAAsTAAALEwEAmpwYAAAF+mlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDIgNzkuMTYwOTI0LCAyMDE3LzA3LzEzLTAxOjA2OjM5ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgKFdpbmRvd3MpIiB4bXA6Q3JlYXRlRGF0ZT0iMjAyMy0wNC0wMVQxNTozODoxOSswMjowMCIgeG1wOk1vZGlmeURhdGU9IjIwMjMtMDQtMDFUMTU6NDE6MDcrMDI6MDAiIHhtcDpNZXRhZGF0YURhdGU9IjIwMjMtMDQtMDFUMTU6NDE6MDcrMDI6MDAiIGRjOmZvcm1hdD0iaW1hZ2UvcG5nIiBwaG90b3Nob3A6Q29sb3JNb2RlPSIzIiBwaG90b3Nob3A6SUNDUHJvZmlsZT0ic1JHQiBJRUM2MTk2Ni0yLjEiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MjZiMGE4MDQtZWYxZS1hMDQ0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'; // Track if initial data has been received let initialDataReceived = false; // Track song progress state let songStartTime = null; let songDurationSeconds = 0; let progressInterval = null; // Show loading indicator const loadingOverlay = document.getElementById('loading-overlay'); const connectionError = document.getElementById('connection-error'); // Initial status load fetchStatus(); // Set up Server-Sent Events for live updates setupEventSource(); // Functions function setupEventSource() { const eventSource = new EventSource('/events'); eventSource.onmessage = function(event) { const data = JSON.parse(event.data); initialDataReceived = true; updateUI(data); // Hide loading indicator once we've received data if (loadingOverlay) { loadingOverlay.classList.add('hidden'); } // Hide connection error message if it was showing if (connectionError) { connectionError.classList.add('hidden'); } }; eventSource.onerror = function() { // If connection fails, close it and retry after a delay eventSource.close(); statusDot.className = 'status-dot offline'; statusText.textContent = 'Connection lost. Retrying...'; // Show connection error message if (connectionError) { connectionError.classList.remove('hidden'); } setTimeout(setupEventSource, 5000); }; } function fetchStatus() { fetch('/status') .then(response => response.json()) .then(data => { initialDataReceived = true; updateUI(data); // Hide loading indicator once we've received data if (loadingOverlay) { loadingOverlay.classList.add('hidden'); } // Hide connection error message if it was showing if (connectionError) { connectionError.classList.add('hidden'); } }) .catch(error => { console.error('Error fetching status:', error); statusDot.className = 'status-dot offline'; statusText.textContent = 'Error connecting to server'; // Show connection error message if (connectionError) { connectionError.classList.remove('hidden'); } }); } function updateUI(data) { // If this is the first data we've received, update everything const isInitialUpdate = !document.getElementById('data-loaded'); if (isInitialUpdate) { // Mark the page as having received data const dataLoadedMark = document.createElement('div'); dataLoadedMark.id = 'data-loaded'; dataLoadedMark.style.display = 'none'; document.body.appendChild(dataLoadedMark); } // Update status indicator if (data.isPlaying) { statusDot.className = 'status-dot online'; statusText.textContent = 'Playing music'; nowPlaying.classList.remove('hidden'); notPlaying.classList.add('hidden'); } else { statusDot.className = 'status-dot offline'; statusText.textContent = 'Not playing'; nowPlaying.classList.add('hidden'); notPlaying.classList.remove('hidden'); // Stop progress bar updates if not playing stopProgressUpdates(); } // Update song information if we have a current song if (data.currentSong) { updateSongInfo(data.currentSong, data.currentSongStarted); } // Update song history if available // Always show history if it exists, even when nothing is playing if (data.songHistory && data.songHistory.length > 0) { updateSongHistory(data.songHistory, data.currentSong); songHistory.classList.remove('hidden'); } else { // Hide history section only if there's no history songHistory.classList.add('hidden'); } // Update last update time if (data.updateTime) { const updateDate = new Date(data.updateTime); lastUpdate.textContent = formatDate(updateDate); } } function updateSongInfo(song, startTimeStr) { // Update the song title songTitle.textContent = song.title || 'Unknown Title'; // Update the artist songArtist.textContent = song.artist || 'Unknown Artist'; // Update the album songAlbum.textContent = song.album || 'Unknown Album'; // Update the duration if available if (song.duration) { const minutes = Math.floor(song.duration / 60); const seconds = song.duration % 60; songDuration.textContent = `${minutes}:${seconds.toString().padStart(2, '0')}`; songDurationSeconds = song.duration; // Update total time in progress bar totalTime.textContent = `${minutes}:${seconds.toString().padStart(2, '0')}`; } else { songDuration.textContent = '--'; songDurationSeconds = 0; totalTime.textContent = '--'; } // Update the genre if available songGenre.textContent = song.genre || '--'; // Update the year if available songYear.textContent = song.year ? song.year.toString() : '--'; // Update the cover art if available if (song.coverArt) { // This assumes your backend has a route to get cover art // You might need to adjust this to match your API const coverArtUrl = `/cover/${song.coverArt}`; coverArt.src = coverArtUrl; coverArt.alt = `${song.album} cover`; } else { coverArt.src = DEFAULT_COVER_ART; coverArt.alt = 'Default cover art'; } // Set up progress bar setupProgressBar(startTimeStr); } function formatDate(date) { return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' }); } // Format seconds to MM:SS format function formatTime(seconds) { const minutes = Math.floor(seconds / 60); const secs = Math.floor(seconds % 60); return `${minutes}:${secs.toString().padStart(2, '0')}`; } // Set up progress bar with song start time function setupProgressBar(startTimeStr) { // Stop any existing progress updates stopProgressUpdates(); // Reset progress bar progressBar.style.width = '0%'; currentTime.textContent = '0:00'; // If we don't have a start time or duration, exit if (!startTimeStr || songDurationSeconds <= 0) { return; } // Parse the start time songStartTime = new Date(startTimeStr); // Start progress updates updateProgress(); progressInterval = setInterval(updateProgress, 1000); } // Update the progress bar function updateProgress() { if (!songStartTime || songDurationSeconds <= 0) { return; } // Calculate elapsed time in seconds const now = new Date(); const elapsedSeconds = (now - songStartTime) / 1000; // Calculate progress percentage const progressPercent = Math.min(100, (elapsedSeconds / songDurationSeconds) * 100); // Update progress bar width progressBar.style.width = `${progressPercent}%`; // Update current time display const displaySeconds = Math.min(songDurationSeconds, elapsedSeconds); currentTime.textContent = formatTime(displaySeconds); // If we've reached the end of the song (with a small buffer) if (elapsedSeconds >= songDurationSeconds + 2) { stopProgressUpdates(); } } // Stop progress updates function stopProgressUpdates() { if (progressInterval) { clearInterval(progressInterval); progressInterval = null; } songStartTime = null; } // Update the song history list function updateSongHistory(songs, currentSong) { // Clear the current list historyList.innerHTML = ''; // If there's no song history yet, show a message if (!songs || songs.length === 0) { const li = document.createElement('li'); li.className = 'history-item history-empty'; li.textContent = 'No song history available yet'; historyList.appendChild(li); return; } // Create a set of songs for the history that excludes the current song const historySongs = []; const currentId = currentSong ? currentSong.id : null; // Filter out null entries and the current song for (const song of songs) { if (!song) continue; if (currentId && song.id === currentId) continue; historySongs.push(song); } // If after filtering, we don't have any songs to display if (historySongs.length === 0) { const li = document.createElement('li'); li.className = 'history-item history-empty'; li.textContent = 'No previous songs to display'; historyList.appendChild(li); return; } // Add each song to the history list for (const song of historySongs) { const li = document.createElement('li'); li.className = 'history-item'; // Create cover art const img = document.createElement('img'); if (song.coverArt) { img.src = `/cover/${song.coverArt}`; } else { img.src = DEFAULT_COVER_ART; } img.alt = 'Cover'; img.className = 'history-cover'; // Create song details const details = document.createElement('div'); details.className = 'history-details'; const title = document.createElement('div'); title.className = 'history-title'; title.textContent = song.title || 'Unknown Title'; const artist = document.createElement('div'); artist.className = 'history-artist'; artist.textContent = song.artist || 'Unknown Artist'; // Assemble the elements details.appendChild(title); details.appendChild(artist); li.appendChild(img); li.appendChild(details); historyList.appendChild(li); } }