// Public Domain document.addEventListener('DOMContentLoaded', function () { const presetContainer = document.getElementById('preset-container'); const stopButton = document.querySelector('.stop-button'); const currentStationName = document.getElementById('current-station-name'); const presetModal = document.getElementById('preset-modal'); const tableBody = document.querySelector('#sortable-tbody'); const editPresetsButton = document.getElementById('edit-presets-button'); const openSleepTimerModalButton = document.getElementById('open-sleep-timer-modal'); const sleepTimerModal = document.getElementById('sleep-timer-modal'); const closeSleepTimerModalButton = document.getElementById('close-sleep-timer-modal'); const offSleepTimerModalButton = document.getElementById('off-sleep-timer-modal'); let currentSound = null; let presets = []; let sleepTimerTimeout = null; let sleepTimerEndTime = null; // Benutzerdefiniertes Modal für Alerts und Bestätigungen function showAlert(title, message) { const modal = document.getElementById("custom-modal"); const titleElement = document.getElementById("custom-modal-title"); const messageElement = document.getElementById("custom-modal-message"); const cancelButton = document.getElementById("custom-modal-cancel"); titleElement.textContent = title; messageElement.textContent = message; cancelButton.classList.add("hidden"); modal.classList.remove("hidden"); document.getElementById("custom-modal-ok").onclick = function() { modal.classList.add("hidden"); }; } function showConfirm(title, message, callback) { const modal = document.getElementById("custom-modal"); const titleElement = document.getElementById("custom-modal-title"); const messageElement = document.getElementById("custom-modal-message"); const cancelButton = document.getElementById("custom-modal-cancel"); titleElement.textContent = title; messageElement.textContent = message; cancelButton.classList.remove("hidden"); modal.classList.remove("hidden"); document.getElementById("custom-modal-ok").onclick = function() { modal.classList.add("hidden"); callback(true); }; cancelButton.onclick = function() { modal.classList.add("hidden"); callback(false); }; } initializeFooter(); loadPresets(); initializeEventListeners(); function initializeFooter() { const currentYear = new Date().getFullYear(); document.getElementById('current-year').textContent = currentYear; document.getElementById('server-name').textContent = window.location.hostname; } function updateTitle(stationName) { document.title = stationName ? `${stationName} - Radioplayer mit Presets` : "Radioplayer mit Presets"; } function initializeEventListeners() { stopButton.addEventListener('click', stopPlayback); editPresetsButton.addEventListener('click', openModal); } async function loadPresets() { presets = JSON.parse(localStorage.getItem('presets')) || []; if (presets.length === 0) { try { const response = await fetch('webradio_presets.json'); if (!response.ok) throw new Error('Datei konnte nicht geladen werden'); presets = await response.json(); localStorage.setItem('presets', JSON.stringify(presets)); } catch (error) { showAlert("Fehler", 'Fehler beim Laden der Presets: ' + error.message); } } displayPresets(presets); renderPresets(); initSortable(); } function displayPresets(presets) { presetContainer.innerHTML = ''; presets.forEach(preset => addNewPreset(preset.name, preset.url)); } function addNewPreset(name, url) { const presetItem = document.createElement('div'); presetItem.classList.add('preset-item'); presetItem.dataset.streamUrl = url; presetItem.innerHTML = `

${name}

`; presetItem.addEventListener('click', () => playStream(url, presetItem)); presetContainer.appendChild(presetItem); } function renderPresets() { tableBody.innerHTML = ''; presets.forEach((preset, index) => { const row = document.createElement('tr'); row.setAttribute('data-index', index); row.innerHTML = ` ☰ `; tableBody.appendChild(row); }); } openSleepTimerModalButton.addEventListener('click', function () { sleepTimerModal.classList.remove('hidden'); sleepTimerModal.style.display = 'block'; updateOffButtonVisibility(); }); closeSleepTimerModalButton.addEventListener('click', function () { sleepTimerModal.classList.add('hidden'); sleepTimerModal.style.display = 'none'; }); offSleepTimerModalButton.addEventListener('click', function () { clearSleepTimer(); sleepTimerModal.classList.add('hidden'); sleepTimerModal.style.display = 'none'; }); document.querySelectorAll('.time-option').forEach(button => { button.addEventListener('click', function () { const timeInMilliseconds = button.getAttribute('data-time'); startSleepTimer(parseInt(timeInMilliseconds)); sleepTimerModal.classList.add('hidden'); sleepTimerModal.style.display = 'none'; }); }); function startSleepTimer(duration) { if (sleepTimerTimeout) { clearTimeout(sleepTimerTimeout); } sleepTimerEndTime = Date.now() + duration; sleepTimerTimeout = setTimeout(() => { stopPlayback(); updateSleepTimerButton(); updateOffButtonVisibility(); }, duration); updateSleepTimerButton(); updateOffButtonVisibility(); } function clearSleepTimer() { if (sleepTimerTimeout) { clearTimeout(sleepTimerTimeout); sleepTimerTimeout = null; sleepTimerEndTime = null; openSleepTimerModalButton.textContent = 'Sleep Timer'; updateOffButtonVisibility(); } } function updateSleepTimerButton() { if (sleepTimerEndTime) { const remainingTime = Math.max(0, Math.ceil((sleepTimerEndTime - Date.now()) / 60000)); openSleepTimerModalButton.innerHTML = ` ${remainingTime} Minuten`; if (remainingTime > 0) { setTimeout(updateSleepTimerButton, 60000); } else { openSleepTimerModalButton.textContent = 'Sleep Timer'; sleepTimerEndTime = null; updateOffButtonVisibility(); } } } function updateOffButtonVisibility() { if (sleepTimerTimeout) { offSleepTimerModalButton.style.display = 'inline-block'; } else { offSleepTimerModalButton.style.display = 'none'; } } function playStream(url, item) { stopPlayback(); currentSound = new Howl({ src: [url], html5: true, volume: 0.5, onplay: () => { stopButton.classList.remove('hidden'); updateTitle(item.querySelector('h3').textContent); currentStationName.textContent = 'Es läuft: ' + item.querySelector('h3').textContent; }, onstop: () => { stopButton.classList.add('hidden'); currentStationName.textContent = 'Wähle einen Sender aus'; updateTitle(''); } }); currentSound.play(); } function stopPlayback() { if (currentSound) { currentSound.stop(); currentSound = null; } } function openModal() { presetModal.classList.remove('hidden'); presetModal.style.display = 'block'; renderPresets(); generateExportCode(); } window.closeModal = function() { presetModal.classList.add('hidden'); presetModal.style.display = 'none'; } window.addPreset = function() { presets.push({ name: 'Neuer Sender', url: '' }); renderPresets(); generateExportCode(); } window.changeName = function(index, newName) { presets[index].name = newName; generateExportCode(); } window.changeURL = function(index, newURL) { presets[index].url = newURL; generateExportCode(); } window.deletePreset = function(index) { showConfirm("Löschen", "Möchtest du diesen Sender wirklich löschen?", function(confirmed) { if (confirmed) { presets.splice(index, 1); renderPresets(); generateExportCode(); } }); } window.saveAndClose = function() { savePresets(); closeModal(); location.reload(); } function savePresets() { localStorage.setItem('presets', JSON.stringify(presets)); } function initSortable() { const sortable = new Sortable(tableBody, { handle: '.drag-handle', animation: 150, onEnd: function (evt) { const newOrder = Array.from(tableBody.children).map(row => parseInt(row.getAttribute('data-index'))); presets = newOrder.map(index => presets[index]); generateExportCode(); } }); } // --- NEUE EXPORT/IMPORT-FUNKTIONEN --- window.generateExportCode = function() { try { const jsonString = JSON.stringify(presets); const base64String = btoa(unescape(encodeURIComponent(jsonString))); const exportField = document.getElementById('export-string-field'); if (exportField) exportField.value = base64String; } catch (e) { console.error("Export-Fehler:", e); showAlert("Fehler", "Export-Fehler: " + e.message); } } // Export String mit Kompression (Gzip) window.generateExportCode = async function() { try { if (!presets || presets.length === 0) return; // 1. Struktur-Diät: Nur Werte speichern [["Name","URL"],["Name","URL"]] const compactData = presets.map(p => [p.name, p.url]); const jsonString = JSON.stringify(compactData); // 2. Kompression mittels Gzip const stream = new Blob([jsonString]).stream(); const compressedStream = stream.pipeThrough(new CompressionStream('gzip')); const response = new Response(compressedStream); const buffer = await response.arrayBuffer(); // 3. In Base64 umwandeln const base64String = btoa(String.fromCharCode(...new Uint8Array(buffer))); const exportField = document.getElementById('export-string-field'); if (exportField) exportField.value = base64String; console.log("Kompression erfolgreich. Länge:", base64String.length); } catch (e) { console.error("Export-Fehler:", e); } } // Export nach JSON-Datei window.exportPreset = function() { const presetsJSON = JSON.stringify(presets, null, 2); const blob = new Blob([presetsJSON], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'radio_presets.json'; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); }; // Import String mit Dekomprimierung window.importFromString = function() { const field = document.getElementById('import-string-field'); const code = field.value.trim(); if (!code) return showAlert("Fehler", "Bitte Code einfügen."); showConfirm("Vorsicht", "Es werden alle bestehenden Sender überschrieben. Möchtest du fortfahren?", async function(confirmed) { if (!confirmed) return; try { // 1. Base64 zurück in Bytes const binary = atob(code); const bytes = Uint8Array.from(binary, c => c.charCodeAt(0)); // 2. Dekomprimieren (Gzip) const stream = new Blob([bytes]).stream(); const decompressedStream = stream.pipeThrough(new DecompressionStream('gzip')); const response = new Response(decompressedStream); const resultText = await response.text(); // 3. Struktur wiederherstellen (Array zu Objekten) const importedArray = JSON.parse(resultText); const restoredPresets = importedArray.map(item => ({ name: item[0], url: item[1] })); if (Array.isArray(restoredPresets)) { presets = restoredPresets; savePresets(); location.reload(); } } catch (e) { console.error("Import-Fehler:", e); showAlert("Fehler", "Code ungültig oder beschädigt!"); } }); }; // Import aus JSON-Datei mit Warnung window.importPreset = function() { showConfirm("Vorsicht", "Es werden alle bestehenden Sender überschrieben. Möchtest du fortfahren?", function(confirmed) { if (!confirmed) return; const input = document.createElement('input'); input.type = 'file'; input.accept = 'application/json'; input.onchange = function(event) { const file = event.target.files[0]; if (file) { const reader = new FileReader(); reader.onload = function(e) { try { const importedPresets = JSON.parse(e.target.result); presets = importedPresets; savePresets(); location.reload(); } catch (error) { showAlert("Fehler", 'Fehler beim Importieren der Presets: Ungültige Datei.'); } }; reader.readAsText(file); } }; input.click(); }); }; });