Update index.js

Code angepasst und auf verschiedene Dateien aufgeteilt.
This commit is contained in:
simonpipe 2025-12-18 17:15:46 +01:00
parent 1f565b62ca
commit 3d4f6bb803

505
index.js
View file

@ -1,214 +1,93 @@
// Public Domain // Public Domain - Hauptlogik des Radioplayers (Schaltzentrale)
document.addEventListener('DOMContentLoaded', function () { document.addEventListener('DOMContentLoaded', function () {
// --- UI ELEMENTE ---
const presetContainer = document.getElementById('preset-container'); const presetContainer = document.getElementById('preset-container');
const stopButton = document.querySelector('.stop-button'); const stopButton = document.querySelector('.stop-button');
const currentStationName = document.getElementById('current-station-name'); const currentStationName = document.getElementById('current-station-name');
const presetModal = document.getElementById('preset-modal'); const presetModal = document.getElementById('preset-modal');
const tableBody = document.querySelector('#sortable-tbody'); 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 currentSound = null;
let presets = [];
let sleepTimerTimeout = null;
let sleepTimerEndTime = null;
// Benutzerdefiniertes Modal für Alerts und Bestätigungen // --- INITIALISIERUNG ---
function showAlert(title, message) { if (window.UI) window.UI.initializeFooter();
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(); initializeEventListeners();
function initializeFooter() { // Manager verknüpfen
const currentYear = new Date().getFullYear(); if (window.SleepTimer) {
document.getElementById('current-year').textContent = currentYear; window.SleepTimer.init(() => stopPlayback());
document.getElementById('server-name').textContent = window.location.hostname;
} }
function updateTitle(stationName) { if (window.PresetManager) {
document.title = stationName ? `${stationName} - Radioplayer mit Presets` : "Radioplayer mit Presets"; window.PresetManager.load((loadedPresets) => {
displayPresets(loadedPresets);
initSortable();
});
} }
// --- EVENT LISTENER ---
function initializeEventListeners() { function initializeEventListeners() {
stopButton.addEventListener('click', stopPlayback); // Stop Button
editPresetsButton.addEventListener('click', openModal); if (stopButton) {
} stopButton.onclick = stopPlayback;
}
async function loadPresets() {
presets = JSON.parse(localStorage.getItem('presets')) || []; // Preset Modal öffnen
if (presets.length === 0) { const editBtn = document.getElementById('edit-presets-button');
try { if (editBtn) {
const response = await fetch('webradio_presets.json'); editBtn.onclick = openPresetModal;
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) { // Sleep Timer Modal öffnen
presetContainer.innerHTML = ''; const sleepBtn = document.getElementById('open-sleep-timer-modal');
presets.forEach(preset => addNewPreset(preset.name, preset.url)); 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) { // Zeit-Optionen im Sleep Timer
const presetItem = document.createElement('div'); document.querySelectorAll('.time-option').forEach(btn => {
presetItem.classList.add('preset-item'); btn.onclick = () => {
presetItem.dataset.streamUrl = url; const minutes = parseInt(btn.getAttribute('data-time')) / 60000;
presetItem.innerHTML = `<h3>${name}</h3>`; window.SleepTimer.start(minutes);
presetItem.addEventListener('click', () => playStream(url, presetItem)); const sm = document.getElementById('sleep-timer-modal');
presetContainer.appendChild(presetItem); sm.classList.add('hidden');
} sm.style.display = 'none';
};
function renderPresets() {
tableBody.innerHTML = '';
presets.forEach((preset, index) => {
const row = document.createElement('tr');
row.setAttribute('data-index', index);
row.innerHTML = `
<td class="drag-handle"></td>
<td><input type="text" value="${preset.name}" onchange="changeName(${index}, this.value)"></td>
<td><input type="text" value="${preset.url}" onchange="changeURL(${index}, this.value)"></td>
<td><button class="delete-btn" onclick="deletePreset(${index})"><i class="fas fa-trash-alt" style="color:grey"></i></button></td>
`;
tableBody.appendChild(row);
}); });
} }
openSleepTimerModalButton.addEventListener('click', function () { // --- PLAYER LOGIK ---
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 = `<i class="fas fa-hourglass-half"></i> ${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) { function playStream(url, item) {
stopPlayback(); stopPlayback();
if (window.MetaHandler) window.MetaHandler.stop();
currentSound = new Howl({ currentSound = new Howl({
src: [url], src: [url],
html5: true, html5: true,
volume: 0.5, volume: 0.5,
onplay: () => { onplay: () => {
stopButton.classList.remove('hidden'); stopButton.classList.remove('hidden');
updateTitle(item.querySelector('h3').textContent); const stationName = item.querySelector('h3').textContent;
currentStationName.textContent = 'Es läuft: ' + 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: () => { onstop: () => {
stopButton.classList.add('hidden'); stopButton.classList.add('hidden');
currentStationName.textContent = 'Wähle einen Sender aus'; currentStationName.textContent = 'Wähle einen Sender aus';
updateTitle(''); if (window.UI) window.UI.updateDocumentTitle('');
if (window.MetaHandler) window.MetaHandler.stop();
} }
}); });
currentSound.play(); 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 = `<h3>${preset.name}</h3>`;
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 = `
<td class="drag-handle"></td>
<td><input type="text" value="${preset.name}" onchange="changeName(${index}, this.value)"></td>
<td><input type="text" value="${preset.url}" onchange="changeURL(${index}, this.value)"></td>
<td><button class="delete-btn" onclick="deletePreset(${index})"><i class="fas fa-trash-alt"></i></button></td>
`;
tableBody.appendChild(row);
});
}
// --- MODAL WRAPPER ---
function openPresetModal() {
if (!presetModal) return;
presetModal.classList.remove('hidden'); presetModal.classList.remove('hidden');
presetModal.style.display = 'block'; presetModal.style.display = 'block';
renderPresets(); renderPresetsTable();
generateExportCode(); if (window.PresetManager) window.PresetManager.generateExportCode();
} }
window.closeModal = function() { function openSleepModal() {
presetModal.classList.add('hidden'); const sm = document.getElementById('sleep-timer-modal');
presetModal.style.display = 'none'; if (sm) {
sm.classList.remove('hidden');
sm.style.display = 'block';
if (window.SleepTimer) window.SleepTimer.updateButtonUI();
}
} }
window.addPreset = function() { // --- GLOBALE HELFER (für HTML onclicks & Manager) ---
presets.push({ name: 'Neuer Sender', url: '' }); window.closeModal = () => {
renderPresets(); if (presetModal) {
generateExportCode(); presetModal.classList.add('hidden');
} presetModal.style.display = 'none';
}
};
window.addPreset = () => {
window.PresetManager.presets.push({ name: 'Neuer Sender', url: '' });
renderPresetsTable();
};
window.changeName = function(index, newName) { window.deletePreset = (index) => {
presets[index].name = newName; window.UI.showConfirm("Löschen", "Sender wirklich entfernen?", (conf) => {
generateExportCode(); if (conf) {
} window.PresetManager.presets.splice(index, 1);
renderPresetsTable();
window.changeURL = function(index, newURL) { window.PresetManager.generateExportCode();
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() { window.saveAndClose = () => {
savePresets(); window.PresetManager.save();
closeModal();
location.reload(); location.reload();
} };
function savePresets() { window.changeName = (i, v) => {
localStorage.setItem('presets', JSON.stringify(presets)); 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() { function initSortable() {
const sortable = new Sortable(tableBody, { if (!tableBody) return;
new Sortable(tableBody, {
handle: '.drag-handle', handle: '.drag-handle',
animation: 150, animation: 150,
onEnd: function (evt) { onEnd: () => {
const newOrder = Array.from(tableBody.children).map(row => parseInt(row.getAttribute('data-index'))); const newOrder = Array.from(tableBody.children).map(row => parseInt(row.getAttribute('data-index')));
presets = newOrder.map(index => presets[index]); window.PresetManager.presets = newOrder.map(index => window.PresetManager.presets[index]);
generateExportCode(); 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();
});
};
});