diff --git a/config/ajaxModule.inc.php b/config/ajaxModule.inc.php index e6d8702..efdcaf1 100644 --- a/config/ajaxModule.inc.php +++ b/config/ajaxModule.inc.php @@ -1,5 +1,7 @@ message) . " (Linea: " . $error->line . ")"); + } + libxml_clear_errors(); + return false; + } + } + + return self::$radioXML; + } + + /** + * Carica e cachea il file XML delle TV + */ + public static function getTVXML() { + if (self::$tvXML === null) { + $xmlPath = './data/tv.xml'; + + if (!file_exists($xmlPath)) { + error_log("File XML non trovato: $xmlPath"); + return false; + } + + if (!is_readable($xmlPath)) { + error_log("File XML non leggibile: $xmlPath"); + return false; + } + + libxml_use_internal_errors(true); + self::$tvXML = simplexml_load_file($xmlPath); + + if (self::$tvXML === false) { + $errors = libxml_get_errors(); + foreach ($errors as $error) { + error_log("Errore XML tv.xml: " . trim($error->message) . " (Linea: " . $error->line . ")"); + } + libxml_clear_errors(); + return false; + } + } + + return self::$tvXML; + } + + /** + * Carica e cachea il file XML del changelog + */ + public static function getChangelogXML() { + if (self::$changelogXML === null) { + $xmlPath = './data/changelog.xml'; + + if (!file_exists($xmlPath)) { + error_log("File XML non trovato: $xmlPath"); + return false; + } + + if (!is_readable($xmlPath)) { + error_log("File XML non leggibile: $xmlPath"); + return false; + } + + libxml_use_internal_errors(true); + self::$changelogXML = simplexml_load_file($xmlPath); + + if (self::$changelogXML === false) { + $errors = libxml_get_errors(); + foreach ($errors as $error) { + error_log("Errore XML changelog.xml: " . trim($error->message) . " (Linea: " . $error->line . ")"); + } + libxml_clear_errors(); + return false; + } + } + + return self::$changelogXML; + } +} + +/** + * Funzione per caricare tutte le stazioni radio + * @return array|SimpleXMLElement Array di stazioni o array vuoto in caso di errore + */ function loadRadioStations() { - $xml = simplexml_load_file('./data/radio.xml'); - if ($xml === false) { - error_log("Errore nel caricamento del file XML: data/radio.xml"); + $xml = StationCache::getRadioXML(); + if ($xml === false || !isset($xml->station)) { + error_log("Impossibile caricare le stazioni radio"); return []; } return $xml->station; } -// Funzione per ottenere una singola stazione radio +/** + * Funzione per ottenere una singola stazione radio + * @param int $id ID della stazione + * @return SimpleXMLElement|null Stazione o null se non trovata + */ function getRadioStation($id) { - $xml = simplexml_load_file('./data/radio.xml'); + $xml = StationCache::getRadioXML(); if ($xml === false) { - error_log("Errore nel caricamento del file XML: data/radio.xml"); + error_log("Impossibile caricare XML radio per ID: $id"); return null; } + + if (!isset($xml->station)) { + error_log("Nessuna stazione trovata nel file XML"); + return null; + } + foreach ($xml->station as $station) { - if ((int)$station->id === $id) { + if ((int)$station->id === (int)$id) { return $station; } } + + error_log("Stazione radio non trovata con ID: $id"); return null; } -// Funzione per caricare il file XML delle TV +/** + * Funzione per caricare tutte le stazioni TV + * @return array|SimpleXMLElement Array di stazioni o array vuoto in caso di errore + */ function loadTVStations() { - $xml = simplexml_load_file('./data/tv.xml'); - if ($xml === false) { - error_log("Errore nel caricamento del file XML: data/tv.xml"); + $xml = StationCache::getTVXML(); + if ($xml === false || !isset($xml->station)) { + error_log("Impossibile caricare le stazioni TV"); return []; } return $xml->station; } -// Funzione per ottenere una singola stazione TV +/** + * Funzione per ottenere una singola stazione TV + * @param int $id ID della stazione + * @return SimpleXMLElement|null Stazione o null se non trovata + */ function getTVStation($id) { - $xml = simplexml_load_file('./data/tv.xml'); + $xml = StationCache::getTVXML(); if ($xml === false) { - error_log("Errore nel caricamento del file XML: data/tv.xml"); + error_log("Impossibile caricare XML TV per ID: $id"); return null; } + + if (!isset($xml->station)) { + error_log("Nessuna stazione TV trovata nel file XML"); + return null; + } + foreach ($xml->station as $station) { - if ((int)$station->id === $id) { + if ((int)$station->id === (int)$id) { return $station; } } + + error_log("Stazione TV non trovata con ID: $id"); return null; } -$changelog = simplexml_load_file("./data/changelog.xml") or die("Errore: Impossibile accedere al file CHANGELOG"); -$version_app = $changelog->version->number[0]; \ No newline at end of file +/** + * Carica il changelog e la versione dell'app + */ +$changelog = StationCache::getChangelogXML(); +$version_app = "1.0.0"; // Versione di default + +if ($changelog !== false && isset($changelog->version) && isset($changelog->version[0]->number)) { + $version_app = (string)$changelog->version[0]->number; +} else { + error_log("Impossibile leggere la versione dal changelog.xml, uso versione di default: $version_app"); +} \ No newline at end of file diff --git a/data/changelog.xml b/data/changelog.xml index 135697e..684199e 100644 --- a/data/changelog.xml +++ b/data/changelog.xml @@ -2,6 +2,15 @@ + + 2.1.0 + + Risoluzione dei problemi minori presenti nel codice, che causava problemi nella navigazione in app + Risoluzione dei problemi minori presenti nel codice, che causava problemi di riproduzione audio al player + Correzione e bugfix di problematiche varie. + + + 2.0.4 diff --git a/index.php b/index.php index 8c35aa2..bdb4cfe 100644 --- a/index.php +++ b/index.php @@ -11,6 +11,8 @@ # Version app: VEDERE IN CHANGELOG.XML */ +header('Content-Type: text/html; charset=UTF-8'); + # Import config file include_once './config/config.php'; diff --git a/js/app.js b/js/app.js index 33a0018..0be8a87 100644 --- a/js/app.js +++ b/js/app.js @@ -4,6 +4,11 @@ document.addEventListener('DOMContentLoaded', function() { let currentPage = getCurrentPageFromPath(); let audioPlayer = null; let hlsInstance = null; + let currentXHR = null; + let isLoading = false; + + // WeakMap per tracciare i listener degli elementi + const linkListeners = new WeakMap(); loadHlsLibrary(); @@ -12,8 +17,8 @@ document.addEventListener('DOMContentLoaded', function() { const script = document.createElement('script'); script.src = 'https://cdn.jsdelivr.net/npm/hls.js@latest'; - script.onload = () => console.log('HLS.js caricato'); - script.onerror = () => console.error('Errore caricamento HLS.js'); + script.onload = () => console.log('HLS.js caricato con successo'); + script.onerror = () => console.error('Errore nel caricamento di HLS.js'); document.head.appendChild(script); } @@ -29,68 +34,162 @@ document.addEventListener('DOMContentLoaded', function() { return path || 'home'; } - function loadPage(page) { - const contentContainer = document.getElementById('content'); - if (!contentContainer) return; + /** + * Pulizia completa del player audio + * Previene memory leak rimuovendo istanze HLS e player audio + */ + function cleanupPlayer() { + console.log('Pulizia player audio...'); + // Distruggi istanza HLS + if (hlsInstance) { + try { + hlsInstance.destroy(); + console.log('HLS instance distrutta'); + } catch (e) { + console.error('Errore nella distruzione di HLS:', e); + } + hlsInstance = null; + } + + // Pulisci audio player + if (audioPlayer) { + try { + audioPlayer.pause(); + audioPlayer.src = ''; + audioPlayer.load(); // Libera risorse + + // Rimuovi tutti gli event listeners clonando il nodo + const parent = audioPlayer.parentNode; + if (parent) { + const newPlayer = audioPlayer.cloneNode(true); + parent.replaceChild(newPlayer, audioPlayer); + } + + console.log('Audio player pulito'); + } catch (e) { + console.error('Errore nella pulizia del player audio:', e); + } + audioPlayer = null; + } + + // Reset Media Session + if ('mediaSession' in navigator) { + navigator.mediaSession.metadata = null; + navigator.mediaSession.playbackState = 'none'; + } + } + + /** + * Caricamento pagina con protezione da race condition + */ + function loadPage(page) { + // Previeni richieste multiple simultanee + if (isLoading) { + console.log('Caricamento già in corso, richiesta ignorata'); + return; + } + + // Annulla richiesta XHR precedente se presente + if (currentXHR) { + console.log('Annullamento richiesta precedente'); + currentXHR.abort(); + currentXHR = null; + } + + isLoading = true; + const contentContainer = document.getElementById('content'); + + if (!contentContainer) { + console.error('Container contenuto non trovato'); + isLoading = false; + return; + } + + // Animazione fade out contentContainer.classList.remove('fade-in'); contentContainer.classList.add('fade-out'); setTimeout(() => { const url = BASE_PATH + '/index.php/' + page; - const xhr = new XMLHttpRequest(); - xhr.open('GET', url, true); - xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); + currentXHR = new XMLHttpRequest(); + currentXHR.open('GET', url, true); + currentXHR.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); - xhr.onload = function() { - if (xhr.status === 200) { - contentContainer.innerHTML = xhr.responseText; + currentXHR.onload = function() { + isLoading = false; + currentXHR = null; + + if (this.status === 200) { + contentContainer.innerHTML = this.responseText; + // Animazione fade in contentContainer.classList.remove('fade-out'); contentContainer.classList.add('fade-in'); + // Aggiorna URL nella history const historyUrl = page === 'home' ? BASE_PATH + '/' : BASE_PATH + '/' + page; history.pushState({page: page}, null, historyUrl); currentPage = page; + // Aggiorna navigazione attiva updateActiveNavigation(page); + // Inizializza componenti specifici della pagina if (page.startsWith('play/')) { - initializePlayer(); + setTimeout(() => initializePlayer(), 100); } if (page.startsWith('playtv/')) { - initializeTVPlayer(); + setTimeout(() => initializeTVPlayer(), 100); } + // Riattacca i listener ai link attachLinkListeners(); + // Inizializza form contatti se presente if (page === 'page/contact') { - initializeContactForm(); + setTimeout(() => initializeContactForm(), 100); } + // Scroll to top window.scrollTo({ top: 0, behavior: 'smooth' }); + + console.log('Pagina caricata:', page); } else { - contentContainer.innerHTML = '

Errore

Impossibile caricare la pagina. Codice errore: ' + xhr.status + '

'; + contentContainer.innerHTML = '

Errore

Impossibile caricare la pagina. Codice errore: ' + this.status + '

'; contentContainer.classList.remove('fade-out'); contentContainer.classList.add('fade-in'); + console.error('Errore caricamento pagina:', this.status); } }; - xhr.onerror = function() { - contentContainer.innerHTML = '

Errore di connessione

Controlla la tua connessione internet.

'; + currentXHR.onerror = function() { + isLoading = false; + currentXHR = null; + contentContainer.innerHTML = '

Errore di connessione

Controlla la tua connessione internet e riprova.

'; contentContainer.classList.remove('fade-out'); contentContainer.classList.add('fade-in'); + console.error('Errore di rete durante il caricamento della pagina'); }; - xhr.send(); + currentXHR.onabort = function() { + isLoading = false; + currentXHR = null; + console.log('Richiesta XHR annullata'); + }; + + currentXHR.send(); }, 200); } + /** + * Aggiorna la navigazione attiva + */ function updateActiveNavigation(page) { const navLinks = document.querySelectorAll('.navLink'); navLinks.forEach(link => { @@ -104,24 +203,24 @@ document.addEventListener('DOMContentLoaded', function() { link.classList.add('active'); } else if (page.startsWith('playtv/') && linkPage === 'tv') { link.classList.add('active'); - } else if (page.startsWith('page/')) { - if (linkPage === page) { - link.classList.add('active'); - } + } else if (page.startsWith('page/') && linkPage === page) { + link.classList.add('active'); } }); } - // NUOVA FUNZIONE: Setup Media Session API + /** + * Setup Media Session API per controlli sistema + */ function setupMediaSession(stationName, stationSlogan, stationLogo, playIcon, pauseIcon) { if (!('mediaSession' in navigator)) { - console.log('Media Session API non supportata'); + console.log('Media Session API non supportata su questo browser'); return; } - console.log('Setup Media Session:', stationName, stationSlogan); + console.log('Configurazione Media Session:', stationName); - // Imposta i metadati + // Imposta i metadati della riproduzione navigator.mediaSession.metadata = new MediaMetadata({ title: stationName, artist: stationSlogan, @@ -136,25 +235,26 @@ document.addEventListener('DOMContentLoaded', function() { ] }); - // Gestisci i controlli del centro notifiche + // Handler per il comando Play dal sistema navigator.mediaSession.setActionHandler('play', () => { - console.log('Media Session: Play richiesto dal centro notifiche'); + console.log('Media Session: Play richiesto'); if (audioPlayer) { audioPlayer.play().then(() => { - // Aggiorna UI if (playIcon && pauseIcon) { playIcon.style.display = 'none'; pauseIcon.style.display = 'block'; } + }).catch(err => { + console.error('Errore play da Media Session:', err); }); } }); + // Handler per il comando Pause dal sistema navigator.mediaSession.setActionHandler('pause', () => { - console.log('Media Session: Pause richiesto dal centro notifiche'); + console.log('Media Session: Pause richiesto'); if (audioPlayer) { audioPlayer.pause(); - // Aggiorna UI if (playIcon && pauseIcon) { playIcon.style.display = 'block'; pauseIcon.style.display = 'none'; @@ -162,14 +262,18 @@ document.addEventListener('DOMContentLoaded', function() { } }); - // Opzionale: gestisci skip (per radio potrebbe non servire) + // Disabilita i controlli non utilizzati per radio streaming navigator.mediaSession.setActionHandler('seekbackward', null); navigator.mediaSession.setActionHandler('seekforward', null); navigator.mediaSession.setActionHandler('previoustrack', null); navigator.mediaSession.setActionHandler('nexttrack', null); + + console.log('Media Session configurata correttamente'); } - // NUOVA FUNZIONE: Aggiorna playback state + /** + * Aggiorna lo stato di riproduzione nella Media Session + */ function updateMediaSessionPlaybackState(state) { if ('mediaSession' in navigator) { navigator.mediaSession.playbackState = state; @@ -177,11 +281,20 @@ document.addEventListener('DOMContentLoaded', function() { } } + /** + * Inizializza il player audio con gestione HLS migliorata + */ function initializePlayer() { - console.log('Inizializzazione player...'); + console.log('Inizializzazione player audio...'); + + // Pulisci eventuali istanze precedenti + cleanupPlayer(); const playPauseBtn = document.getElementById('playPauseBtn'); - if (!playPauseBtn) return; + if (!playPauseBtn) { + console.error('Pulsante play/pause non trovato'); + return; + } const playIcon = document.querySelector('.play-icon'); const pauseIcon = document.querySelector('.pause-icon'); @@ -192,29 +305,24 @@ document.addEventListener('DOMContentLoaded', function() { const stationLogo = playPauseBtn.getAttribute('data-station-logo'); const statusElement = document.getElementById('playerStatus'); - console.log('Station:', stationName); - console.log('HLS URL:', hlsUrl); - console.log('Fallback URL:', fallbackUrl); + console.log('Stazione:', stationName); + console.log('URL HLS:', hlsUrl); + console.log('URL Fallback:', fallbackUrl); - // Setup Media Session all'inizio passando anche le icone + // Setup Media Session setupMediaSession(stationName, stationSlogan, stationLogo, playIcon, pauseIcon); - if (hlsInstance) { - hlsInstance.destroy(); - hlsInstance = null; - } - - if (audioPlayer) { - audioPlayer.pause(); - audioPlayer = null; - } - + // Ottieni riferimento al player audio audioPlayer = document.getElementById('hlsAudioPlayer'); if (!audioPlayer) { - audioPlayer = new Audio(); + console.error('Elemento audio player non trovato nel DOM'); + return; } audioPlayer.preload = 'none'; + /** + * Aggiorna lo stato visualizzato + */ function updateStatus(msg, isError = false) { console.log('Status:', msg); if (statusElement) { @@ -223,31 +331,56 @@ document.addEventListener('DOMContentLoaded', function() { } } + /** + * Verifica supporto HLS nativo (Safari) + */ function canPlayHLS() { const video = document.createElement('video'); return video.canPlayType('application/vnd.apple.mpegurl') !== ''; } + /** + * Setup HLS.js con gestione errori robusta + */ function setupHLS() { if (!window.Hls || !Hls.isSupported()) { - console.log('HLS.js non disponibile'); + console.log('HLS.js non disponibile o non supportato'); return false; } - console.log('Uso HLS.js'); - updateStatus('Caricamento...'); + console.log('Utilizzo HLS.js per lo streaming'); + updateStatus('Caricamento stream...'); - hlsInstance = new Hls({ + const hlsConfig = { enableWorker: true, - lowLatencyMode: true - }); + lowLatencyMode: true, + maxBufferLength: 30, + maxMaxBufferLength: 60, + maxBufferSize: 60 * 1000 * 1000, + manifestLoadingTimeOut: 10000, + manifestLoadingMaxRetry: 3, + manifestLoadingRetryDelay: 500, + levelLoadingTimeOut: 10000, + levelLoadingMaxRetry: 4, + fragLoadingTimeOut: 20000, + fragLoadingMaxRetry: 6 + }; + + hlsInstance = new Hls(hlsConfig); + + let networkErrorCount = 0; + let mediaErrorCount = 0; + const MAX_NETWORK_ERRORS = 3; + const MAX_MEDIA_ERRORS = 2; hlsInstance.loadSource(hlsUrl); hlsInstance.attachMedia(audioPlayer); hlsInstance.on(Hls.Events.MANIFEST_PARSED, function() { - console.log('HLS manifest caricato'); - updateStatus('Pronto'); + console.log('Manifest HLS caricato con successo'); + updateStatus('Pronto per la riproduzione'); + networkErrorCount = 0; + mediaErrorCount = 0; }); hlsInstance.on(Hls.Events.ERROR, function(event, data) { @@ -256,40 +389,82 @@ document.addEventListener('DOMContentLoaded', function() { if (data.fatal) { switch(data.type) { case Hls.ErrorTypes.NETWORK_ERROR: - console.log('Errore rete, riprovo...'); - hlsInstance.startLoad(); + networkErrorCount++; + console.log(`Errore di rete ${networkErrorCount}/${MAX_NETWORK_ERRORS}`); + + if (networkErrorCount < MAX_NETWORK_ERRORS) { + updateStatus('Riconnessione in corso...'); + // Backoff esponenziale + setTimeout(() => { + console.log('Tentativo di riconnessione...'); + hlsInstance.startLoad(); + }, 1000 * networkErrorCount); + } else { + console.error('Troppi errori di rete, utilizzo stream fallback'); + updateStatus('Utilizzo stream alternativo...', true); + hlsInstance.destroy(); + hlsInstance = null; + useFallback(); + } break; + case Hls.ErrorTypes.MEDIA_ERROR: - console.log('Errore media, riprovo...'); - hlsInstance.recoverMediaError(); + mediaErrorCount++; + console.log(`Errore media ${mediaErrorCount}/${MAX_MEDIA_ERRORS}`); + + if (mediaErrorCount < MAX_MEDIA_ERRORS) { + updateStatus('Recupero errore media...'); + hlsInstance.recoverMediaError(); + } else { + console.error('Troppi errori media, utilizzo stream fallback'); + updateStatus('Utilizzo stream alternativo...', true); + hlsInstance.destroy(); + hlsInstance = null; + useFallback(); + } break; + default: - console.error('Errore fatale, uso fallback'); + console.error('Errore fatale non recuperabile:', data.details); + updateStatus('Stream non disponibile', true); hlsInstance.destroy(); hlsInstance = null; useFallback(); break; } + } else { + // Errori non fatali + console.warn('Errore HLS non fatale:', data.details); } }); return true; } + /** + * Utilizza stream fallback MP3 + */ function useFallback() { - console.log('Uso fallback:', fallbackUrl); + console.log('Utilizzo stream fallback MP3:', fallbackUrl); if (fallbackUrl) { - audioPlayer.src = fallbackUrl; - audioPlayer.load(); - updateStatus('Pronto (MP3)'); + try { + audioPlayer.src = fallbackUrl; + audioPlayer.load(); + updateStatus('Pronto (MP3)'); + } catch (e) { + console.error('Errore nel caricamento del fallback:', e); + updateStatus('Stream non disponibile', true); + } } else { + console.error('Nessuno stream fallback disponibile'); updateStatus('Stream non disponibile', true); } } + // Inizializza lo streaming if (hlsUrl) { if (canPlayHLS()) { - console.log('Uso HLS nativo (Safari)'); + console.log('Utilizzo supporto HLS nativo (Safari)'); audioPlayer.src = hlsUrl; updateStatus('Pronto'); } else if (!setupHLS()) { @@ -299,10 +474,10 @@ document.addEventListener('DOMContentLoaded', function() { useFallback(); } - // Gestore pulsante play/pause + // Event handler per il pulsante play/pause playPauseBtn.addEventListener('click', function() { if (audioPlayer.paused) { - updateStatus('Connessione...'); + updateStatus('Connessione allo stream...'); audioPlayer.play() .then(() => { @@ -310,25 +485,28 @@ document.addEventListener('DOMContentLoaded', function() { pauseIcon.style.display = 'block'; updateStatus('In riproduzione'); updateMediaSessionPlaybackState('playing'); + console.log('Riproduzione avviata'); }) .catch(error => { console.error('Errore nella riproduzione:', error); updateStatus('Errore riproduzione', true); updateMediaSessionPlaybackState('paused'); + // Tentativo con fallback se HLS fallisce if (hlsInstance && fallbackUrl) { - console.log('Tentativo con fallback...'); + console.log('Tentativo con stream fallback...'); hlsInstance.destroy(); hlsInstance = null; useFallback(); + setTimeout(() => { audioPlayer.play().catch(e => { - console.error('Fallback fallito:', e); - alert('Impossibile riprodurre lo stream audio. Riprova più tardi.'); + console.error('Anche il fallback è fallito:', e); + alert('Impossibile riprodurre lo stream audio. Verifica la tua connessione e riprova.'); }); }, 500); } else { - alert('Impossibile riprodurre lo stream audio. Riprova più tardi.'); + alert('Impossibile riprodurre lo stream audio. Verifica la tua connessione e riprova.'); } }); } else { @@ -337,14 +515,14 @@ document.addEventListener('DOMContentLoaded', function() { pauseIcon.style.display = 'none'; updateStatus('In pausa'); updateMediaSessionPlaybackState('paused'); + console.log('Riproduzione in pausa'); } }); - // Event listeners con Media Session updates e sincronizzazione UI + // Event listeners per sincronizzazione UI e Media Session audioPlayer.addEventListener('play', function() { updateMediaSessionPlaybackState('playing'); updateStatus('In riproduzione'); - // Sincronizza UI quando l'audio parte (da qualsiasi fonte) playIcon.style.display = 'none'; pauseIcon.style.display = 'block'; }); @@ -352,7 +530,6 @@ document.addEventListener('DOMContentLoaded', function() { audioPlayer.addEventListener('pause', function() { updateMediaSessionPlaybackState('paused'); updateStatus('In pausa'); - // Sincronizza UI quando l'audio va in pausa (da qualsiasi fonte) playIcon.style.display = 'block'; pauseIcon.style.display = 'none'; }); @@ -364,27 +541,48 @@ document.addEventListener('DOMContentLoaded', function() { updateMediaSessionPlaybackState('paused'); }); - audioPlayer.addEventListener('error', function() { + audioPlayer.addEventListener('error', function(e) { playIcon.style.display = 'block'; pauseIcon.style.display = 'none'; updateStatus('Errore stream', true); updateMediaSessionPlaybackState('paused'); + console.error('Errore audio player:', e); alert('Si è verificato un errore durante la riproduzione. Riprova più tardi.'); }); - audioPlayer.addEventListener('waiting', () => updateStatus('Buffering...')); - audioPlayer.addEventListener('playing', () => updateStatus('In riproduzione')); + audioPlayer.addEventListener('waiting', () => { + updateStatus('Buffering...'); + }); - console.log('Player inizializzato con Media Session'); + audioPlayer.addEventListener('playing', () => { + updateStatus('In riproduzione'); + }); + + audioPlayer.addEventListener('stalled', () => { + console.warn('Stream stalled'); + updateStatus('Connessione lenta...'); + }); + + console.log('Player audio inizializzato correttamente'); } + /** + * Inizializza player TV + */ function initializeTVPlayer() { console.log('TV Player inizializzato'); + // Implementare logica specifica per TV se necessario } + /** + * Inizializza form contatti + */ function initializeContactForm() { const contactForm = document.getElementById('contactForm'); - if (!contactForm) return; + if (!contactForm) { + console.log('Form contatti non trovato'); + return; + } contactForm.addEventListener('submit', function(e) { e.preventDefault(); @@ -392,53 +590,78 @@ document.addEventListener('DOMContentLoaded', function() { const formData = new FormData(contactForm); const formResponse = document.getElementById('formResponse'); - formResponse.innerHTML = 'Invio in corso...'; - formResponse.className = 'form-response'; - formResponse.style.display = 'block'; + if (formResponse) { + formResponse.innerHTML = 'Invio in corso...'; + formResponse.className = 'form-response'; + formResponse.style.display = 'block'; + } const xhr = new XMLHttpRequest(); xhr.open('POST', BASE_PATH + '/process_contact.php', true); + xhr.onload = function() { if (xhr.status === 200) { try { const response = JSON.parse(xhr.responseText); if (response.success) { - formResponse.innerHTML = 'Messaggio inviato con successo! Ti risponderemo presto.'; - formResponse.className = 'form-response success'; + if (formResponse) { + formResponse.innerHTML = 'Messaggio inviato con successo! Ti risponderemo presto.'; + formResponse.className = 'form-response success'; + } contactForm.reset(); } else { - formResponse.innerHTML = 'Errore: ' + (response.message || 'Si è verificato un errore durante l\'invio del messaggio.'); - formResponse.className = 'form-response error'; + if (formResponse) { + formResponse.innerHTML = 'Errore: ' + (response.message || 'Si è verificato un errore durante l\'invio del messaggio.'); + formResponse.className = 'form-response error'; + } } } catch (e) { - formResponse.innerHTML = 'Si è verificato un errore durante l\'elaborazione della risposta.'; - formResponse.className = 'form-response error'; + console.error('Errore parsing risposta:', e); + if (formResponse) { + formResponse.innerHTML = 'Si è verificato un errore durante l\'elaborazione della risposta.'; + formResponse.className = 'form-response error'; + } } } else { - formResponse.innerHTML = 'Si è verificato un errore durante l\'invio del messaggio.'; + if (formResponse) { + formResponse.innerHTML = 'Si è verificato un errore durante l\'invio del messaggio.'; + formResponse.className = 'form-response error'; + } + } + }; + + xhr.onerror = function() { + if (formResponse) { + formResponse.innerHTML = 'Errore di connessione. Controlla la tua connessione internet.'; formResponse.className = 'form-response error'; } }; - xhr.onerror = function() { - formResponse.innerHTML = 'Errore di connessione. Controlla la tua connessione internet.'; - formResponse.className = 'form-response error'; - }; + xhr.send(formData); }); + + console.log('Form contatti inizializzato'); } + /** + * Attacca event listener ai link con gestione migliorata + */ function attachLinkListeners() { const navLinks = document.querySelectorAll('.navLink, .nav-link, .station-link, .linkBox'); navLinks.forEach(link => { - const newLink = link.cloneNode(true); - link.parentNode.replaceChild(newLink, link); + // Rimuovi listener precedente se esiste + const oldListener = linkListeners.get(link); + if (oldListener) { + link.removeEventListener('click', oldListener); + } - newLink.addEventListener('click', function(e) { + const clickHandler = function(e) { const href = this.getAttribute('href'); const target = this.getAttribute('target'); const page = this.getAttribute('data-page'); + // Verifica se è un link esterno const isExternal = href && ( href.startsWith('http://') || href.startsWith('https://') || @@ -447,27 +670,35 @@ document.addEventListener('DOMContentLoaded', function() { ); if (isExternal) { - console.log('Link esterno rilevato:', href); - e.preventDefault(); - window.location.href = href; - return; + console.log('Link esterno:', href); + return; // Lascia che il browser gestisca normalmente } e.preventDefault(); e.stopPropagation(); - console.log('Link cliccato, pagina:', page, 'currentPage:', currentPage); + console.log('Navigazione a:', page); - if (page && page !== currentPage) { + // Naviga solo se la pagina è diversa e non stiamo già caricando + if (page && page !== currentPage && !isLoading) { + // Metti in pausa l'audio se in riproduzione if (audioPlayer && !audioPlayer.paused) { audioPlayer.pause(); } loadPage(page); } - }); + }; + + link.addEventListener('click', clickHandler); + linkListeners.set(link, clickHandler); }); + + console.log('Event listener attaccati a', navLinks.length, 'link'); } + /** + * Setup pulsante apertura app (desktop) + */ function setupOpenAppButton() { const openAppBtn = document.getElementById('openAppBtn'); if (openAppBtn) { @@ -486,9 +717,14 @@ document.addEventListener('DOMContentLoaded', function() { const windowFeatures = 'width=' + width + ',height=' + height + ',left=' + left + ',top=' + top + ',resizable=yes,scrollbars=yes,status=yes'; window.open(BASE_PATH + '/?app=true' + redirectParam, 'RadioApp', windowFeatures); }); + + console.log('Pulsante apertura app configurato'); } } + /** + * Setup navigazione con history API + */ function setupHistoryNavigation() { window.addEventListener('popstate', function(e) { if (e.state && e.state.page) { @@ -503,76 +739,89 @@ document.addEventListener('DOMContentLoaded', function() { }); history.replaceState({page: currentPage}, null, window.location.pathname); + console.log('Navigazione history configurata'); } + /** + * Setup modalità standalone (PWA) + */ function setupStandaloneMode() { const appContainer = document.querySelector('.container'); if (!appContainer) return; if (window.opener && !window.opener.closed) { appContainer.classList.add('standalone-app'); + console.log('App in modalità finestra popup'); } if (window.navigator.standalone || window.matchMedia('(display-mode: standalone)').matches) { appContainer.classList.add('standalone-app'); + console.log('App in modalità standalone (PWA)'); } } - function init() { - setupOpenAppButton(); - - if (document.querySelector('.container')) { - setupStandaloneMode(); - - attachLinkListeners(); - - setupHistoryNavigation(); - - initializeCurrentPage(); - } - } - + /** + * Inizializza la pagina corrente + */ function initializeCurrentPage() { console.log('Inizializzazione pagina corrente:', currentPage); updateActiveNavigation(currentPage); if (currentPage.startsWith('play/')) { - setTimeout(function() { - initializePlayer(); - }, 100); + setTimeout(() => initializePlayer(), 100); } if (currentPage.startsWith('playtv/')) { - setTimeout(function() { - initializeTVPlayer(); - }, 100); + setTimeout(() => initializeTVPlayer(), 100); } if (currentPage === 'page/contact') { - setTimeout(function() { - initializeContactForm(); - }, 100); + setTimeout(() => initializeContactForm(), 100); } } + /** + * Inizializzazione principale + */ + function init() { + console.log('Inizializzazione app...'); + + setupOpenAppButton(); + + if (document.querySelector('.container')) { + setupStandaloneMode(); + attachLinkListeners(); + setupHistoryNavigation(); + initializeCurrentPage(); + } + + console.log('App inizializzata correttamente'); + } + + // Avvia l'inizializzazione init(); }); -// Funzione per bloccare l'orientamento (funziona solo in PWA/fullscreen) +/** + * GESTIONE ORIENTAMENTO SCHERMO + */ + +/** + * Blocca l'orientamento in portrait (funziona solo in PWA/fullscreen) + */ function lockScreenOrientation() { - // Prova a bloccare l'orientamento con Screen Orientation API + // Screen Orientation API if (screen.orientation && screen.orientation.lock) { screen.orientation.lock('portrait').then(() => { console.log('Orientamento bloccato in portrait'); }).catch((err) => { console.log('Impossibile bloccare orientamento:', err.message); - // Non è un errore grave, il CSS gestirà la situazione }); } - // Alternativa per browser più vecchi + // Fallback per browser più vecchi if (screen.lockOrientation) { screen.lockOrientation('portrait'); } else if (screen.mozLockOrientation) { @@ -582,9 +831,20 @@ function lockScreenOrientation() { } } -// Prova a bloccare quando l'app è in fullscreen/standalone +/** + * Gestisce il cambio di orientamento + */ +function handleOrientationChange() { + const orientation = window.orientation || (screen.orientation && screen.orientation.angle) || 0; + console.log('Orientamento schermo:', orientation); + + // Puoi aggiungere logica specifica qui se necessario +} + +/** + * Tenta di bloccare l'orientamento quando appropriato + */ function tryLockOrientation() { - // Controlla se siamo in modalità standalone (PWA) const isStandalone = window.navigator.standalone || window.matchMedia('(display-mode: standalone)').matches; @@ -592,7 +852,6 @@ function tryLockOrientation() { lockScreenOrientation(); } - // Prova anche quando entriamo in fullscreen document.addEventListener('fullscreenchange', () => { if (document.fullscreenElement) { lockScreenOrientation(); diff --git a/pages/desktop.php b/pages/desktop.php index d8aec28..22be86f 100644 --- a/pages/desktop.php +++ b/pages/desktop.php @@ -1,3 +1,5 @@ + +