document.addEventListener('DOMContentLoaded', function () { const BASE_PATH = window.BASE_PATH || ''; let currentPage = getCurrentPageFromPath(); let audioPlayer = null; let hlsInstance = null; let currentXHR = null; let isLoading = false; let loadingOverlay = null; // WeakMap per tracciare i listener degli elementi const linkListeners = new WeakMap(); loadHlsLibrary(); function loadHlsLibrary() { if (window.Hls) return; const script = document.createElement('script'); script.src = 'https://cdn.jsdelivr.net/npm/hls.js@latest'; script.onload = () => console.log('HLS.js caricato con successo'); script.onerror = () => console.error('Errore nel caricamento di HLS.js'); document.head.appendChild(script); } function getCurrentPageFromPath() { let path = window.location.pathname; // Rimuovi il base path se presente if (BASE_PATH && path.startsWith(BASE_PATH)) { path = path.substring(BASE_PATH.length); } // Rimuovi slash iniziali e finali path = path.replace(/^\/|\/$/g, ''); // IMPORTANTE: Se il path è vuoto o è solo "index.php", controlla il parametro redirect if (!path || path === '' || path === 'index.php') { const urlParams = new URLSearchParams(window.location.search); const redirect = urlParams.get('redirect'); if (redirect) { console.log('📍 Trovato parametro redirect:', redirect); return redirect; } } console.log('📍 Path rilevato:', path || 'home'); return path || 'home'; } /** * 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'; } } /** * Mostra l'overlay di caricamento */ function showLoadingOverlay() { if (!loadingOverlay) { loadingOverlay = document.getElementById('loadingOverlay'); } if (loadingOverlay) { loadingOverlay.classList.add('active'); } } /** * Nasconde l'overlay di caricamento */ function hideLoadingOverlay() { if (loadingOverlay) { loadingOverlay.classList.remove('active'); } } /** * 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; } // Mostra l'overlay di caricamento showLoadingOverlay(); // Animazione fade out contentContainer.classList.remove('fade-in'); contentContainer.classList.add('fade-out'); setTimeout(() => { const url = BASE_PATH + '/index.php/' + page; currentXHR = new XMLHttpRequest(); currentXHR.open('GET', url, true); currentXHR.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); currentXHR.onload = function () { isLoading = false; currentXHR = null; if (this.status === 200) { contentContainer.innerHTML = this.responseText; // Nascondi overlay di caricamento hideLoadingOverlay(); // 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/')) { setTimeout(() => initializePlayer(), 100); } if (page.startsWith('playtv/')) { setTimeout(() => initializeTVPlayer(), 100); } // Riattacca i listener ai link attachLinkListeners(); // Inizializza form contatti se presente if (page === 'page/contact') { setTimeout(() => initializeContactForm(), 100); } // Scroll to top window.scrollTo({ top: 0, behavior: 'smooth' }); console.log('Pagina caricata:', page); } else { hideLoadingOverlay(); 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); } }; currentXHR.onerror = function () { isLoading = false; currentXHR = null; hideLoadingOverlay(); 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'); }; currentXHR.onabort = function () { isLoading = false; currentXHR = null; hideLoadingOverlay(); console.log('Richiesta XHR annullata'); }; currentXHR.send(); }, 200); } /** * Aggiorna la navigazione attiva */ function updateActiveNavigation(page) { console.log('Aggiornamento navigazione per pagina:', page); // Debug const navLinks = document.querySelectorAll('.navLink'); navLinks.forEach(link => { link.classList.remove('active'); const linkPage = link.getAttribute('data-page'); // Log di debug per vedere i confronti console.log('Confronto:', linkPage, 'con', page); if (linkPage === page) { link.classList.add('active'); console.log('✓ Match esatto:', linkPage); } else if (page.startsWith('play/') && linkPage === 'radio') { link.classList.add('active'); console.log('✓ Match play/ -> radio'); } else if (page.startsWith('playtv/') && linkPage === 'tv') { link.classList.add('active'); console.log('✓ Match playtv/ -> tv'); } else if (page.startsWith('page/') && linkPage === page) { link.classList.add('active'); console.log('✓ Match page/'); } }); } /** * 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 su questo browser'); return; } console.log('Configurazione Media Session:', stationName); // Imposta i metadati della riproduzione navigator.mediaSession.metadata = new MediaMetadata({ title: stationName, artist: stationSlogan, album: 'RPIGroup Play', artwork: [ { src: stationLogo, sizes: '96x96', type: 'image/png' }, { src: stationLogo, sizes: '128x128', type: 'image/png' }, { src: stationLogo, sizes: '192x192', type: 'image/png' }, { src: stationLogo, sizes: '256x256', type: 'image/png' }, { src: stationLogo, sizes: '384x384', type: 'image/png' }, { src: stationLogo, sizes: '512x512', type: 'image/png' } ] }); // Handler per il comando Play dal sistema navigator.mediaSession.setActionHandler('play', () => { console.log('Media Session: Play richiesto'); if (audioPlayer) { audioPlayer.play().then(() => { 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'); if (audioPlayer) { audioPlayer.pause(); if (playIcon && pauseIcon) { playIcon.style.display = 'block'; pauseIcon.style.display = 'none'; } } }); // 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'); } /** * Aggiorna lo stato di riproduzione nella Media Session */ function updateMediaSessionPlaybackState(state) { if ('mediaSession' in navigator) { navigator.mediaSession.playbackState = state; console.log('Media Session playback state:', state); } } /** * Inizializza il player audio con gestione HLS migliorata */ function initializePlayer() { console.log('Inizializzazione player audio (v2.0 - Hybrid Strategy)...'); const playPauseBtn = document.getElementById('playPauseBtn'); const playIcon = document.querySelector('.play-icon'); const pauseIcon = document.querySelector('.pause-icon'); const playerStatus = document.getElementById('playerStatus'); if (!playPauseBtn) return; // 1. Cleanup Preventivo cleanupPlayer(); // 2. Setup Elementi audioPlayer = document.getElementById('hlsAudioPlayer'); if (!audioPlayer) return; const streamHLS = playPauseBtn.getAttribute('data-stream-hls'); const streamFallback = playPauseBtn.getAttribute('data-stream-fallback'); const stationName = playPauseBtn.getAttribute('data-station-name'); let isPlaying = false; // Configurazione HLS.js Ottimizzata per Qualità const hlsConfig = { debug: false, enableWorker: true, lowLatencyMode: false, // Disabilitato per stabilità e buffer migliore backBufferLength: 90, maxBufferLength: 30, // Aumentato buffer (default 30s) startLevel: -1, // Auto start xhrSetup: function (xhr, url) { xhr.withCredentials = false; // Fix CORS issues sometimes } }; /** * Logica di Selezione Player * Priorità: HLS.js (PC/Android) > Nativo (iOS) > Fallback (Direct Stream) */ function initStream() { // STRATEGIA 1: HLS.js (Preferita per PC e Android) if (Hls.isSupported()) { console.log('Strategy: HLS.js (High Quality Control)'); hlsInstance = new Hls(hlsConfig); hlsInstance.loadSource(streamHLS); hlsInstance.attachMedia(audioPlayer); hlsInstance.on(Hls.Events.MANIFEST_PARSED, function (event, data) { console.log(`HLS Manifest Loaded. Levels: ${data.levels.length}`); // Log dettagliato di tutti i livelli disponibili per debug data.levels.forEach((level, index) => { console.log(`Level ${index}: ${level.bitrate} bps (${(level.bitrate / 1000).toFixed(0)} kbps)`); }); // CRITICAL: Forza partenza dal livello massimo per garantire qualità alta if (data.levels.length > 1) { const maxLevel = data.levels.length - 1; hlsInstance.startLevel = maxLevel; console.log(`✓ Forced START level to MAX: ${maxLevel} (${(data.levels[maxLevel].bitrate / 1000).toFixed(0)} kbps)`); } attemptPlay(); }); // Track quality switches per debug hlsInstance.on(Hls.Events.LEVEL_SWITCHING, function (event, data) { console.log(`→ Switching to level ${data.level}...`); }); hlsInstance.on(Hls.Events.LEVEL_SWITCHED, function (event, data) { const currentLevel = hlsInstance.levels[data.level]; console.log(`✓ Switched to level ${data.level}: ${(currentLevel.bitrate / 1000).toFixed(0)} kbps`); }); hlsInstance.on(Hls.Events.ERROR, function (event, data) { if (data.fatal) { console.warn('HLS Fatal Error:', data.type); switch (data.type) { case Hls.ErrorTypes.NETWORK_ERROR: hlsInstance.startLoad(); break; case Hls.ErrorTypes.MEDIA_ERROR: hlsInstance.recoverMediaError(); break; default: fallbackToDirectStream(); break; } } }); return; } // STRATEGIA 2: Nativo (Obbligatorio per iOS) if (audioPlayer.canPlayType('application/vnd.apple.mpegurl')) { console.log('Strategy: Native HLS (iOS/Safari)'); audioPlayer.src = streamHLS; audioPlayer.preload = 'auto'; // Suggerisce al browser di caricare dati // Hack per iOS: Alcune volte serve un tocco utente, gestito dal click play attemptPlay(); return; } // STRATEGIA 3: Fallback (Stream MP3 diretto) console.log('Strategy: Direct Fallback'); fallbackToDirectStream(); } function fallbackToDirectStream() { if (hlsInstance) { hlsInstance.destroy(); hlsInstance = null; } audioPlayer.src = streamFallback; attemptPlay(); } function attemptPlay() { // Nota: chiamare .play() senza interazione utente fallirà. // Questa funzione prepara lo stato. Il vero play avviene al click. if (playerStatus) playerStatus.textContent = 'Pronto alla riproduzione'; } function togglePlay() { if (audioPlayer.paused) { const playPromise = audioPlayer.play(); if (playPromise !== undefined) { playPromise.then(() => { updateUI(true); }).catch(error => { console.error('Play prevented:', error); // Se HLS fallisce al play (es. stale manifest), prova fallback if (!audioPlayer.src.includes(streamFallback) && !hlsInstance) { fallbackToDirectStream(); audioPlayer.play().catch(e => console.error('Fallback failed:', e)); } }); } } else { audioPlayer.pause(); updateUI(false); } } function updateUI(playing) { isPlaying = playing; playIcon.style.display = playing ? 'none' : 'block'; pauseIcon.style.display = playing ? 'block' : 'none'; if (playerStatus) { playerStatus.textContent = playing ? 'In riproduzione' : 'In pausa'; } } // Listeners playPauseBtn.onclick = (e) => { e.preventDefault(); // Se non inizializzato (primo click), avvia stream if (!audioPlayer.src && !hlsInstance) { initStream(); // Piccolo timeout per dare tempo al setup prima del play effettivo setTimeout(togglePlay, 50); } else { togglePlay(); } }; audioPlayer.onplay = () => updateUI(true); audioPlayer.onpause = () => updateUI(false); audioPlayer.onerror = (e) => { console.error('Audio Error:', audioPlayer.error); if (playerStatus) playerStatus.textContent = 'Errore riproduzione'; updateUI(false); }; // Setup Media Session setupMediaSession(stationName, playPauseBtn.getAttribute('data-station-slogan'), playPauseBtn.getAttribute('data-station-logo'), playIcon, pauseIcon); console.log('Player System Ready.'); } /** * 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) { console.log('Form contatti non trovato'); return; } contactForm.addEventListener('submit', function (e) { e.preventDefault(); const formData = new FormData(contactForm); const formResponse = document.getElementById('formResponse'); 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) { if (formResponse) { formResponse.innerHTML = 'Messaggio inviato con successo! Ti risponderemo presto.'; formResponse.className = 'form-response success'; } contactForm.reset(); } else { if (formResponse) { formResponse.innerHTML = 'Errore: ' + (response.message || 'Si è verificato un errore durante l\'invio del messaggio.'); formResponse.className = 'form-response error'; } } } catch (e) { 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 { 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.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 => { // Rimuovi listener precedente se esiste const oldListener = linkListeners.get(link); if (oldListener) { link.removeEventListener('click', oldListener); } 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://') || href.startsWith('//') || target === '_blank' ); if (isExternal) { console.log('Link esterno:', href); return; // Lascia che il browser gestisca normalmente } e.preventDefault(); e.stopPropagation(); console.log('Navigazione a:', page); // 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) { openAppBtn.addEventListener('click', function (e) { e.preventDefault(); const currentPath = window.location.pathname; const pathWithoutBase = currentPath.replace(BASE_PATH, '').replace(/^\//, ''); const redirectParam = pathWithoutBase ? '&redirect=' + encodeURIComponent(pathWithoutBase) : ''; const width = 375; const height = 667; const left = (window.screen.width / 2) - (width / 2); const top = (window.screen.height / 2) - (height / 2); 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) { if (e.state.page !== currentPage) { loadPage(e.state.page); } } else { if (currentPage !== 'home') { loadPage('home'); } } }); 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)'); } } /** * Inizializza la pagina corrente */ function initializeCurrentPage() { console.log('Inizializzazione pagina corrente:', currentPage); updateActiveNavigation(currentPage); if (currentPage.startsWith('play/')) { setTimeout(() => initializePlayer(), 100); } if (currentPage.startsWith('playtv/')) { setTimeout(() => initializeTVPlayer(), 100); } if (currentPage === 'page/contact') { 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(); }); /** * GESTIONE ORIENTAMENTO SCHERMO */ /** * Blocca l'orientamento in portrait (funziona solo in PWA/fullscreen) */ function lockScreenOrientation() { // 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); }); } // Fallback per browser più vecchi if (screen.lockOrientation) { screen.lockOrientation('portrait'); } else if (screen.mozLockOrientation) { screen.mozLockOrientation('portrait'); } else if (screen.msLockOrientation) { screen.msLockOrientation('portrait'); } } /** * 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() { const isStandalone = window.navigator.standalone || window.matchMedia('(display-mode: standalone)').matches; if (isStandalone) { lockScreenOrientation(); } document.addEventListener('fullscreenchange', () => { if (document.fullscreenElement) { lockScreenOrientation(); } }); } // Event listeners per cambio orientamento window.addEventListener('orientationchange', handleOrientationChange); window.addEventListener('resize', handleOrientationChange); // Inizializza al caricamento document.addEventListener('DOMContentLoaded', () => { tryLockOrientation(); handleOrientationChange(); }); // Inizializza subito se il DOM è già pronto if (document.readyState !== 'loading') { tryLockOrientation(); handleOrientationChange(); }