// read the CSS variable from :root (or any selector) const cssVar = getComputedStyle(document.documentElement).getPropertyValue('--dark-background').trim(); // player DOM elements const nowPlayingInfo = document.getElementById('nowPlayingInfo'); const audioPlayer = document.getElementById('globalAudio'); const audioPlayerContainer = document.getElementById('audioPlayerContainer'); const playerButton = document.querySelector('.player-button'), audio = document.querySelector('audio'), timeline = document.querySelector('.timeline'), timeInfo = document.getElementById('timeInfo'), playIcon = ` `, pauseIcon = ` `, soundIcon = ` ` function toggleAudio () { if (audio.paused) { audio.play(); playerButton.innerHTML = pauseIcon; } else { audio.pause(); playerButton.innerHTML = playIcon; } } playerButton.addEventListener('click', toggleAudio); let isSeeking = false; // --- Slider (Timeline) events --- // Mouse events timeline.addEventListener('mousedown', () => { isSeeking = true; }); timeline.addEventListener('mouseup', () => { isSeeking = false; changeSeek(); // Update the audio currentTime based on the slider position }); // Touch events timeline.addEventListener('touchstart', () => { isSeeking = true; }); timeline.addEventListener('touchend', () => { isSeeking = false; changeSeek(); }); timeline.addEventListener('touchcancel', () => { isSeeking = false; }); // --- When metadata is loaded, set slider range in seconds --- audio.addEventListener('loadedmetadata', () => { timeline.min = 0; timeline.max = audio.duration; }); // --- Seek function: directly set audio.currentTime using slider's value (in seconds) --- function changeSeek() { audio.currentTime = timeline.value; } // --- Utility: Format seconds as mm:ss --- function formatTime(seconds) { const minutes = Math.floor(seconds / 60); const secs = Math.floor(seconds % 60); return `${minutes < 10 ? '0' : ''}${minutes}:${secs < 10 ? '0' : ''}${secs}`; } // --- Update timeline, time info, and media session on each time update --- audio.ontimeupdate = function() { // --- Update the slider thumb position (in seconds) while playing --- if (!isSeeking) { timeline.value = audio.currentTime; const percentagePosition = (audio.currentTime / audio.duration) * 100; timeline.style.backgroundSize = `${percentagePosition}% 100%`; } // --- Update the time display --- const currentTimeFormatted = formatTime(audio.currentTime); const durationFormatted = isNaN(audio.duration) ? "00:00" : formatTime(audio.duration); timeInfo.innerHTML = `${currentTimeFormatted} / ${durationFormatted}`; if ('mediaSession' in navigator && 'setPositionState' in navigator.mediaSession && !isNaN(audio.duration)) { navigator.mediaSession.setPositionState({ duration: audio.duration, playbackRate: audio.playbackRate, position: audio.currentTime }); } }; // --- When audio ends --- audio.onended = function() { playerButton.innerHTML = playIcon; }; async function downloadAudio() { const audio = document.getElementById('globalAudio'); const src = audio.currentSrc || audio.src; if (!src) return; // Build the URL with your download flag + cache‑buster const downloadUrl = new URL(src, window.location.href); downloadUrl.searchParams.set('download', 'true'); downloadUrl.searchParams.set('_', Date.now()); // Create a “real” link to that URL and click it const a = document.createElement('a'); a.href = downloadUrl.toString(); // NOTE: do NOT set a.download here – we want the server's Content-Disposition to drive it document.body.appendChild(a); a.click(); document.body.removeChild(a); } // Global variable to store the current fetch's AbortController. let currentFetchController = null; async function startPlaying(relUrl) { // Pause the audio and clear its source. audioPlayer.pause(); audioPlayer.src = ''; // Display the audio player container. audioPlayerContainer.style.display = "block"; // Set a timeout to display a loader message if needed. const loaderTimeout = setTimeout(() => { playerButton.innerHTML = playIcon; nowPlayingInfo.textContent = "Wird geladen..."; }, 250); const spinnerTimer = setTimeout(showSpinner, 500); footer.style.display = 'flex'; // Abort any previous fetch if still running. if (currentFetchController) { currentFetchController.abort(); } currentFetchController = new AbortController(); const mediaUrl = `/media/${relUrl}`; try { // Perform a HEAD request to verify media availability. const response = await fetch(mediaUrl, { method: 'HEAD', signal: currentFetchController.signal }); clearTimeout(loaderTimeout); if (response.status === 403) { nowPlayingInfo.textContent = "Fehler: Zugriff verweigert."; window.location.href = '/'; return; } else if (!response.ok) { nowPlayingInfo.textContent = `Fehler: Unerwarteter Status (${response.status}).`; console.error('Unexpected response status:', response.status); return; } // Set the media URL, load, and play the audio. audioPlayer.src = mediaUrl; audioPlayer.load(); await audioPlayer.play(); clearTimeout(spinnerTimer); hideSpinner(); currentTrackPath = relUrl; playerButton.innerHTML = pauseIcon; // Process file path for display. const pathParts = relUrl.split('/'); const folderName = pathParts[pathParts.length - 2]; const fileName = pathParts.pop(); const pathStr = pathParts.join('/'); // Update Media Session metadata if available. if ('mediaSession' in navigator) { navigator.mediaSession.metadata = new MediaMetadata({ title: fileName.replace(/\.[^/.]+$/, ''), // remove extension artist: folderName, artwork: [ { src: '/icons/logo-192x192.png', sizes: '192x192', type: 'image/png' } ] }); } nowPlayingInfo.innerHTML = pathStr.replace(/\//g, ' > ') + '
' + fileName.replace('.mp3', '') + ''; } catch (error) { if (error.name === 'AbortError') { console.log('Previous fetch aborted.'); } else { console.error('Error fetching media:', error); nowPlayingInfo.textContent = "Fehler: Netzwerkproblem oder ungültige URL."; } }; }