diff --git a/index.js b/index.js
index b551f8f..63721ad 100644
--- a/index.js
+++ b/index.js
@@ -1,214 +1,93 @@
-// Public Domain
+// Public Domain - Hauptlogik des Radioplayers (Schaltzentrale)
document.addEventListener('DOMContentLoaded', function () {
+ // --- UI ELEMENTE ---
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();
+ // --- INITIALISIERUNG ---
+ if (window.UI) window.UI.initializeFooter();
initializeEventListeners();
- function initializeFooter() {
- const currentYear = new Date().getFullYear();
- document.getElementById('current-year').textContent = currentYear;
- document.getElementById('server-name').textContent = window.location.hostname;
+ // Manager verknüpfen
+ if (window.SleepTimer) {
+ window.SleepTimer.init(() => stopPlayback());
}
- function updateTitle(stationName) {
- document.title = stationName ? `${stationName} - Radioplayer mit Presets` : "Radioplayer mit Presets";
+ if (window.PresetManager) {
+ window.PresetManager.load((loadedPresets) => {
+ displayPresets(loadedPresets);
+ initSortable();
+ });
}
+ // --- EVENT LISTENER ---
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);
- }
+ // Stop Button
+ if (stopButton) {
+ stopButton.onclick = stopPlayback;
+ }
+
+ // Preset Modal öffnen
+ const editBtn = document.getElementById('edit-presets-button');
+ if (editBtn) {
+ editBtn.onclick = openPresetModal;
}
- displayPresets(presets);
- renderPresets();
- initSortable();
- }
- function displayPresets(presets) {
- presetContainer.innerHTML = '';
- presets.forEach(preset => addNewPreset(preset.name, preset.url));
- }
+ // Sleep Timer Modal öffnen
+ const sleepBtn = document.getElementById('open-sleep-timer-modal');
+ if (sleepBtn) {
+ sleepBtn.onclick = openSleepModal;
+ }
+
+ // Sleep Timer Modal schließen (X-Button)
+ const closeSleepBtn = document.getElementById('close-sleep-timer-modal');
+ if (closeSleepBtn) {
+ closeSleepBtn.onclick = () => {
+ const sm = document.getElementById('sleep-timer-modal');
+ sm.classList.add('hidden');
+ sm.style.display = 'none';
+ };
+ }
- 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);
+ // Zeit-Optionen im Sleep Timer
+ document.querySelectorAll('.time-option').forEach(btn => {
+ btn.onclick = () => {
+ const minutes = parseInt(btn.getAttribute('data-time')) / 60000;
+ window.SleepTimer.start(minutes);
+ const sm = document.getElementById('sleep-timer-modal');
+ sm.classList.add('hidden');
+ sm.style.display = 'none';
+ };
});
}
- 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';
- }
- }
-
+ // --- PLAYER LOGIK ---
function playStream(url, item) {
stopPlayback();
+ if (window.MetaHandler) window.MetaHandler.stop();
+
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;
+ const stationName = item.querySelector('h3').textContent;
+ if (window.UI) window.UI.updateDocumentTitle(stationName);
+ currentStationName.textContent = 'Es läuft: ' + stationName;
+ if (window.MetaHandler) window.MetaHandler.start(url);
},
onstop: () => {
stopButton.classList.add('hidden');
currentStationName.textContent = 'Wähle einen Sender aus';
- updateTitle('');
+ if (window.UI) window.UI.updateDocumentTitle('');
+ if (window.MetaHandler) window.MetaHandler.stop();
}
});
currentSound.play();
@@ -221,185 +100,133 @@ document.addEventListener('DOMContentLoaded', function () {
}
}
- function openModal() {
+ // --- PRESET UI ---
+ function displayPresets(presets) {
+ presetContainer.innerHTML = '';
+ presets.forEach(preset => {
+ const presetItem = document.createElement('div');
+ presetItem.classList.add('preset-item');
+ presetItem.innerHTML = `${preset.name}
`;
+ presetItem.onclick = () => playStream(preset.url, presetItem);
+ presetContainer.appendChild(presetItem);
+ });
+ }
+
+ function renderPresetsTable() {
+ if (!tableBody) return;
+ tableBody.innerHTML = '';
+ window.PresetManager.presets.forEach((preset, index) => {
+ const row = document.createElement('tr');
+ row.setAttribute('data-index', index);
+ row.innerHTML = `
+ ☰ |
+ |
+ |
+ |
+ `;
+ tableBody.appendChild(row);
+ });
+ }
+
+ // --- MODAL WRAPPER ---
+ function openPresetModal() {
+ if (!presetModal) return;
presetModal.classList.remove('hidden');
presetModal.style.display = 'block';
- renderPresets();
- generateExportCode();
+ renderPresetsTable();
+ if (window.PresetManager) window.PresetManager.generateExportCode();
}
- window.closeModal = function() {
- presetModal.classList.add('hidden');
- presetModal.style.display = 'none';
+ function openSleepModal() {
+ const sm = document.getElementById('sleep-timer-modal');
+ if (sm) {
+ sm.classList.remove('hidden');
+ sm.style.display = 'block';
+ if (window.SleepTimer) window.SleepTimer.updateButtonUI();
+ }
}
- window.addPreset = function() {
- presets.push({ name: 'Neuer Sender', url: '' });
- renderPresets();
- generateExportCode();
- }
+ // --- GLOBALE HELFER (für HTML onclicks & Manager) ---
+ window.closeModal = () => {
+ if (presetModal) {
+ presetModal.classList.add('hidden');
+ presetModal.style.display = 'none';
+ }
+ };
+
+ window.addPreset = () => {
+ window.PresetManager.presets.push({ name: 'Neuer Sender', url: '' });
+ renderPresetsTable();
+ };
- 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.deletePreset = (index) => {
+ window.UI.showConfirm("Löschen", "Sender wirklich entfernen?", (conf) => {
+ if (conf) {
+ window.PresetManager.presets.splice(index, 1);
+ renderPresetsTable();
+ window.PresetManager.generateExportCode();
}
});
- }
+ };
- window.saveAndClose = function() {
- savePresets();
- closeModal();
+ window.saveAndClose = () => {
+ window.PresetManager.save();
location.reload();
- }
+ };
- function savePresets() {
- localStorage.setItem('presets', JSON.stringify(presets));
- }
+ window.changeName = (i, v) => {
+ window.PresetManager.presets[i].name = v;
+ window.PresetManager.generateExportCode();
+ };
+
+ window.changeURL = (i, v) => {
+ window.PresetManager.presets[i].url = v;
+ window.PresetManager.generateExportCode();
+ };
+
+ window.exportPreset = () => window.PresetManager.exportToFile();
+
+ window.importPreset = () => {
+ window.UI.showConfirm("Vorsicht", "Alle Sender werden überschrieben!", (conf) => {
+ if (conf) {
+ const input = document.createElement('input');
+ input.type = 'file';
+ input.accept = 'application/json';
+ input.onchange = e => {
+ const reader = new FileReader();
+ reader.onload = ev => {
+ try {
+ window.PresetManager.presets = JSON.parse(ev.target.result);
+ window.PresetManager.save();
+ location.reload();
+ } catch (err) { window.UI.showAlert("Fehler", "Ungültige Datei."); }
+ };
+ reader.readAsText(e.target.files[0]);
+ };
+ input.click();
+ }
+ });
+ };
+
+ window.importFromString = () => {
+ const field = document.getElementById('import-string-field');
+ const code = field ? field.value.trim() : "";
+ if (!code) return window.UI.showAlert("Fehler", "Bitte Code einfügen.");
+ window.UI.showConfirm("Vorsicht", "Alle Sender werden überschrieben!", (conf) => {
+ if (conf) window.PresetManager.importFromString(code);
+ });
+ };
function initSortable() {
- const sortable = new Sortable(tableBody, {
+ if (!tableBody) return;
+ new Sortable(tableBody, {
handle: '.drag-handle',
animation: 150,
- onEnd: function (evt) {
+ onEnd: () => {
const newOrder = Array.from(tableBody.children).map(row => parseInt(row.getAttribute('data-index')));
- presets = newOrder.map(index => presets[index]);
- generateExportCode();
+ window.PresetManager.presets = newOrder.map(index => window.PresetManager.presets[index]);
+ window.PresetManager.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();
- });
- };
-});
+});
\ No newline at end of file