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;
let metadataInterval = 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...');
// Stop metadata updates
if (metadataInterval) {
clearInterval(metadataInterval);
metadataInterval = null;
}
// 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.1 - Format Toggle)...');
const playPauseBtn = document.getElementById('playPauseBtn');
const playIcon = document.querySelector('.play-icon');
const pauseIcon = document.querySelector('.pause-icon');
const playerStatus = document.getElementById('playerStatus');
const formatToggleBtn = document.getElementById('formatToggleBtn');
const formatLabel = document.getElementById('formatLabel');
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');
// Stato formato audio (preferenza salvata in localStorage)
const storageKey = 'audioFormat_' + stationName;
let currentFormat = localStorage.getItem(storageKey) || 'hls'; // 'hls' o 'direct'
let isPlaying = false;
// Aggiorna label del formato
function updateFormatLabel() {
if (formatLabel) {
formatLabel.textContent = currentFormat === 'hls' ? 'HLS' : 'MP3';
}
}
updateFormatLabel();
// 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(forceFormat = null) {
const preferredFormat = forceFormat || currentFormat;
// Se l'utente ha scelto formato diretto, usa subito MP3/AAC
if (preferredFormat === 'direct') {
console.log('User preference: Direct stream (MP3/AAC)');
useDirectStream();
return;
}
// 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:
useDirectStream();
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');
useDirectStream();
}
function useDirectStream() {
if (hlsInstance) {
hlsInstance.destroy();
hlsInstance = null;
}
console.log('Using direct stream:', streamFallback);
audioPlayer.src = streamFallback;
attemptPlay();
}
// Funzione per cambiare formato
function toggleFormat() {
const wasPlaying = !audioPlayer.paused;
// Cambia formato
currentFormat = currentFormat === 'hls' ? 'direct' : 'hls';
localStorage.setItem(storageKey, currentFormat);
console.log('Switching to format:', currentFormat);
// Aggiorna label
updateFormatLabel();
// Ferma riproduzione corrente
if (audioPlayer) {
audioPlayer.pause();
}
// Pulisci player precedente
if (hlsInstance) {
hlsInstance.destroy();
hlsInstance = null;
}
audioPlayer.src = '';
// Reinizializza con nuovo formato
initStream(currentFormat);
// Riprendi riproduzione se era in corso
if (wasPlaying) {
setTimeout(() => {
togglePlay();
}, 100);
}
}
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);
};
// Format toggle button listener
if (formatToggleBtn) {
formatToggleBtn.onclick = (e) => {
e.preventDefault();
toggleFormat();
};
}
// Setup Media Session
setupMediaSession(stationName, playPauseBtn.getAttribute('data-station-slogan'), playPauseBtn.getAttribute('data-station-logo'), playIcon, pauseIcon);
// --- GESTIONE METADATI (Now Playing) ---
const apiUrl = playPauseBtn.getAttribute('data-api-url');
if (apiUrl) {
console.log('Starting metadata polling for:', apiUrl);
function updateMetadata() {
fetch(apiUrl)
.then(response => response.json())
.then(data => {
if (data.now_playing && data.now_playing.song) {
const song = data.now_playing.song;
// Aggiorna DOM
const songsEl = document.getElementById('songs');
const artistEl = document.getElementById('artist');
const albumArtEl = document.getElementById('albumsong');
if (songsEl) songsEl.textContent = song.title;
if (artistEl) artistEl.textContent = song.artist;
if (albumArtEl && song.art) {
// Evita refresh se l'immagine è la stessa
if (albumArtEl.src !== song.art) {
albumArtEl.src = song.art;
}
}
// Aggiorna Media Session
if ('mediaSession' in navigator) {
navigator.mediaSession.metadata = new MediaMetadata({
title: song.title,
artist: song.artist,
album: stationName,
artwork: [
{ src: song.art || playPauseBtn.getAttribute('data-station-logo'), sizes: '512x512', type: 'image/png' }
]
});
}
}
})
.catch(err => console.error('Error fetching metadata:', err));
}
// Chiamata immediata
updateMetadata();
// Polling ogni 10 secondi
metadataInterval = setInterval(updateMetadata, 10000);
}
console.log('Player System Ready with format toggle.');
}
/**
* Inizializza player TV
*/
function initializeTVPlayer() {
console.log('TV Player inizializzato');
const video = document.getElementById('tvVideoPlayer');
if (!video) {
console.log('Elemento video TV non trovato');
return;
}
// Cleanup istanza precedente se esiste
if (video.hlsInstance) {
video.hlsInstance.destroy();
delete video.hlsInstance;
}
const videoSrc = video.getAttribute('data-src');
console.log('Video Source:', videoSrc);
if (!videoSrc) {
console.error('Nessuna sorgente video specificata in data-src');
return;
}
// Creazione elemento per errori se non esiste già
let errorDisplay = video.parentNode.querySelector('.tv-error-display');
if (!errorDisplay) {
errorDisplay = document.createElement('div');
errorDisplay.className = 'tv-error-display';
errorDisplay.style.display = 'none';
errorDisplay.style.position = 'absolute';
errorDisplay.style.top = '50%';
errorDisplay.style.left = '50%';
errorDisplay.style.transform = 'translate(-50%, -50%)';
errorDisplay.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
errorDisplay.style.color = '#fff';
errorDisplay.style.padding = '20px';
errorDisplay.style.borderRadius = '8px';
errorDisplay.style.textAlign = 'center';
errorDisplay.style.zIndex = '2000';
errorDisplay.style.maxWidth = '90%';
if (video.parentNode) {
video.parentNode.appendChild(errorDisplay);
// Assicurati che il parent sia relative per il posizionamento absolute
if (getComputedStyle(video.parentNode).position === 'static') {
video.parentNode.style.position = 'relative';
}
}
}
function showError(msg, details) {
console.error(msg, details);
errorDisplay.innerHTML = '