// Define global variables to track music files and the current index. let currentMusicFiles = []; // Array of objects with at least { path, index } let currentMusicIndex = -1; // Index of the current music file let currentTrackPath = ""; // Helper function: decode each segment then re-encode to avoid double encoding. function encodeSubpath(subpath) { if (!subpath) return ''; return subpath .split('/') .map(segment => encodeURIComponent(decodeURIComponent(segment))) .join('/'); } // Global variable for gallery images (updated from current folder) let currentGalleryImages = []; function paintFile() { // Highlight the currently playing file if (currentTrackPath) { const currentMusicFile = currentMusicFiles.find(file => file.path === currentTrackPath); if (currentMusicFile) { const currentMusicFileElement = document.querySelector(`.play-file[data-url="${currentMusicFile.path}"]`); if (currentMusicFileElement) { currentMusicFileElement.closest('.file-item').classList.add('currently-playing'); } } } } function renderContent(data) { // Render breadcrumbs, directories (grid view when appropriate), and files. let breadcrumbHTML = ''; data.breadcrumbs.forEach((crumb, index) => { breadcrumbHTML += `${crumb.name}`; if (index < data.breadcrumbs.length - 1) { breadcrumbHTML += `>`; } }); document.getElementById('breadcrumbs').innerHTML = breadcrumbHTML; // Render directories. let contentHTML = ''; if (data.directories.length > 0) { contentHTML += '
Error loading directory!
${error}
`; throw error; // Propagate the error }); } // preload the next audio file function preload_audio() { // Prefetch the next file by triggering the backend diskcache. if (currentMusicIndex >= 0 && currentMusicIndex < currentMusicFiles.length - 1) { const nextFile = currentMusicFiles[currentMusicIndex + 1]; const nextMediaUrl = '/media/' + nextFile.path; // Use a HEAD request so that the backend reads and caches the file without returning the full content. fetch(nextMediaUrl, { method: 'HEAD' }) // .then(response => { // console.log('Backend diskcache initiated for next file:', nextFile.path); // }) // .catch(error => console.error('Error initiating backend diskcache for next file:', error)); } } // Attach event listeners for directory, breadcrumb, file, and transcript links. function attachEventListeners() { // Directory link clicks. document.querySelectorAll('.directory-link').forEach(link => { link.addEventListener('click', function (event) { event.preventDefault(); const newPath = this.getAttribute('data-path'); loadDirectory(newPath); history.pushState({ subpath: newPath }, '', newPath ? '/path/' + newPath : '/'); // Scroll to the top of the page after click: window.scrollTo({ top: 0, behavior: 'smooth' }); }); }); // Breadcrumb link clicks. document.querySelectorAll('.breadcrumb-link').forEach(link => { link.addEventListener('click', function (event) { event.preventDefault(); const newPath = this.getAttribute('data-path'); loadDirectory(newPath); history.pushState({ subpath: newPath }, '', newPath ? '/path/' + newPath : '/'); }); }); // Cache common DOM elements const nowPlayingInfo = document.getElementById('nowPlayingInfo'); const audioPlayer = document.getElementById('globalAudio'); const playerButton = document.querySelector('.player-button'); const audioPlayerContainer = document.getElementById('audioPlayerContainer'); const footer = document.querySelector('footer'); // Global variable to store the current fetch's AbortController. let currentFetchController = null; document.querySelectorAll('.play-file').forEach(link => { link.addEventListener('click', async function (event) { event.preventDefault(); const { fileType, url: relUrl, index } = this.dataset; // Remove the class from all file items. document.querySelectorAll('.file-item').forEach(item => { item.classList.remove('currently-playing'); }); if (fileType === 'music') { // Update the current music index. currentMusicIndex = index !== undefined ? parseInt(index) : -1; // Display the audio player container. audioPlayerContainer.style.display = "block"; // Mark the clicked item as currently playing. this.closest('.file-item').classList.add('currently-playing'); // Abort any previous fetch if still running. if (currentFetchController) { currentFetchController.abort(); } currentFetchController = new AbortController(); // Pause the audio and clear its source. audioPlayer.pause(); audioPlayer.src = ''; // Set a timeout to display a loader message if needed. const loaderTimeout = setTimeout(() => { playerButton.innerHTML = playIcon; nowPlayingInfo.textContent = "Wird geladen..."; }, 500); footer.style.display = 'flex'; 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(); 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: currentMusicFiles[currentMusicIndex].title, artist: folderName, artwork: [ { src: '/static/icons/logo-192x192.png', sizes: '192x192', type: 'image/png' } ] }); } nowPlayingInfo.innerHTML = pathStr.replace(/\//g, ' > ') + 'Error loading transcription.
'; document.getElementById('transcriptModal').style.display = 'block'; }); }); }); } // Modal close logic for transcript modal. document.addEventListener('DOMContentLoaded', function () { const closeBtn = document.querySelector('#transcriptModal .close'); closeBtn.addEventListener('click', function () { document.getElementById('transcriptModal').style.display = 'none'; }); window.addEventListener('click', function (event) { if (event.target == document.getElementById('transcriptModal')) { document.getElementById('transcriptModal').style.display = 'none'; } }); // Load initial directory based on URL. let initialSubpath = ''; if (window.location.pathname.indexOf('/path/') === 0) { initialSubpath = window.location.pathname.substring(6); // remove "/path/" } loadDirectory(initialSubpath); }); // Handle back/forward navigation. window.addEventListener('popstate', function (event) { const subpath = event.state ? event.state.subpath : ''; loadDirectory(subpath); }); let isReloadButtonVisible = true; // Boolean to track the visibility of the reload button. function reloadDirectory() { if (!isReloadButtonVisible) return Promise.resolve(); // Extract the current path from the URL. let currentSubpath = ''; if (window.location.pathname.indexOf('/path/') === 0) { currentSubpath = window.location.pathname.substring(6); } // Return the promise so that we can chain actions after the directory has reloaded. return loadDirectory(currentSubpath) .then(() => { // Animate the reload button as before. const reloadBtn = document.querySelector('#reload-button'); const reloadBtnSVG = document.querySelector('#reload-button svg'); void reloadBtnSVG.offsetWidth; // Force reflow to reset the animation reloadBtnSVG.classList.add("rotate"); isReloadButtonVisible = false; setTimeout(() => { reloadBtnSVG.classList.remove("rotate"); reloadBtn.style.transition = 'opacity 0.5s ease'; reloadBtn.style.opacity = '1'; reloadBtn.style.pointer = 'cursor'; isReloadButtonVisible = true; }, 10000); }); } if ('mediaSession' in navigator) { // Handler for the play action navigator.mediaSession.setActionHandler('play', () => { document.getElementById('globalAudio').play(); }); // Handler for the pause action navigator.mediaSession.setActionHandler('pause', () => { document.getElementById('globalAudio').pause(); }); // Handler for the previous track action navigator.mediaSession.setActionHandler('previoustrack', () => { if (currentMusicIndex > 0) { const prevFile = currentMusicFiles[currentMusicIndex - 1]; const prevLink = document.querySelector(`.play-file[data-url="${prevFile.path}"]`); if (prevLink) { prevLink.click(); } } }); // Handler for the next track action navigator.mediaSession.setActionHandler('nexttrack', () => { if (currentMusicIndex >= 0 && currentMusicIndex < currentMusicFiles.length - 1) { const nextFile = currentMusicFiles[currentMusicIndex + 1]; const nextLink = document.querySelector(`.play-file[data-url="${nextFile.path}"]`); if (nextLink) { nextLink.click(); } } }); } document.getElementById('globalAudio').addEventListener('ended', () => { reloadDirectory().then(() => { // If we had a track playing, try to find it in the updated list. if (currentTrackPath) { const newIndex = currentMusicFiles.findIndex(file => file.path === currentTrackPath); currentMusicIndex = newIndex; } // Now, if there's a next track, auto-play it. if (currentMusicIndex >= 0 && currentMusicIndex < currentMusicFiles.length - 1) { const nextFile = currentMusicFiles[currentMusicIndex + 1]; const nextLink = document.querySelector(`.play-file[data-url="${nextFile.path}"]`); if (nextLink) { nextLink.click(); } } }).catch(error => { console.error('Error during reload:', error); }); }); // document.addEventListener("DOMContentLoaded", function() { // // Automatically reload every 5 minutes (300,000 milliseconds) // setInterval(reloadDirectory, 300000); // });