// 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 // 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 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+'
'; }); } // 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 : '/'); }); }); // 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 = this.getAttribute('data-file-type'); const relUrl = this.getAttribute('data-url'); const nowPlayingInfo = document.getElementById('nowPlayingInfo'); // Remove the class from all file items. document.querySelectorAll('.file-item').forEach(item => { item.classList.remove('currently-playing'); }); if (fileType === 'music') { const idx = this.getAttribute('data-index'); const audioPlayer = document.getElementById('globalAudio'); const playerButton = document.querySelector('.player-button'); // Update currentMusicIndex based on the clicked element. currentMusicIndex = idx !== null ? parseInt(idx) : -1; // Show the audio player container. document.getElementById('audioPlayerContainer').style.display = "block" // Add the class to the clicked file item's parent. this.closest('.file-item').classList.add('currently-playing'); // Abort any previous fetch if it is still running. if (currentFetchController) { currentFetchController.abort(); } // Create a new AbortController for the current fetch. currentFetchController = new AbortController(); audioPlayer.pause(); audioPlayer.src = ''; // Clear existing source. // only show this if it takes longer. avoid flicker let loaderTimeout = setTimeout(() => { playerButton.innerHTML = playIcon; nowPlayingInfo.textContent = "Wird geladen..."; }, 500); document.querySelector('footer').style.display = 'flex'; const mediaUrl = '/media/' + relUrl; try { // Pass the signal to the fetch so it can be aborted. const response = await fetch(mediaUrl, { method: 'HEAD', signal: currentFetchController.signal }); clearTimeout(loaderTimeout); // done loading. stop timeout if (response.status === 403) { nowPlayingInfo.textContent = "Fehler: Zugriff verweigert."; window.location.href = '/'; // Redirect if forbidden. } else if (!response.ok) { nowPlayingInfo.textContent = `Fehler: Unerwarteter Status (${response.status}).`; console.error('Unexpected response status:', response.status); } else { audioPlayer.src = mediaUrl; audioPlayer.load(); audioPlayer.play(); playerButton.innerHTML = pauseIcon; const pathParts = relUrl.split('/'); const folderName = pathParts[pathParts.length - 2]; const fileName = pathParts.pop(); const pathStr = pathParts.join('/'); // write into hardware player 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); }); 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(); } } }); } // auto-play on audio 'ended' event logic document.getElementById('globalAudio').addEventListener('ended', () => { // Check if there's a next file in the array. if (currentMusicIndex >= 0 && currentMusicIndex < currentMusicFiles.length - 1) { const nextFile = currentMusicFiles[currentMusicIndex + 1]; // Find the corresponding play link (or directly trigger playback). const nextLink = document.querySelector(`.play-file[data-url="${nextFile.path}"]`); if (nextLink) { nextLink.click(); } } });