Files
rpigroupplay/js/app.js
2026-01-28 22:45:13 +01:00

856 lines
30 KiB
JavaScript

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;
// 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';
}
}
/**
* 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;
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;
// 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 {
contentContainer.innerHTML = '<div class="content-page"><h2>Errore</h2><p>Impossibile caricare la pagina. Codice errore: ' + this.status + '</p></div>';
contentContainer.classList.remove('fade-out');
contentContainer.classList.add('fade-in');
console.error('Errore caricamento pagina:', this.status);
}
};
currentXHR.onerror = function () {
isLoading = false;
currentXHR = null;
contentContainer.innerHTML = '<div class="content-page"><h2>Errore di connessione</h2><p>Controlla la tua connessione internet e riprova.</p></div>';
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;
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...');
const playPauseBtn = document.getElementById('playPauseBtn');
const playIcon = document.querySelector('.play-icon');
const pauseIcon = document.querySelector('.pause-icon');
const playerStatus = document.getElementById('playerStatus');
const artistElement = document.getElementById('artist');
if (!playPauseBtn) {
console.error('Pulsante play/pause non trovato');
return;
}
// Pulisci il player precedente
cleanupPlayer();
// Ottieni il nuovo audio player dal DOM
audioPlayer = document.getElementById('hlsAudioPlayer');
if (!audioPlayer) {
console.error('Elemento audio non trovato');
return;
}
const streamHLS = playPauseBtn.getAttribute('data-stream-hls');
const streamFallback = playPauseBtn.getAttribute('data-stream-fallback');
const stationName = playPauseBtn.getAttribute('data-station-name');
const stationSlogan = playPauseBtn.getAttribute('data-station-slogan');
const stationLogo = playPauseBtn.getAttribute('data-station-logo');
console.log('Stream HLS:', streamHLS);
console.log('Stream Fallback:', streamFallback);
console.log('Stazione:', stationName);
let isPlaying = false;
let streamType = 'hls'; // 'hls' o 'direct'
/**
* Inizializza HLS stream
*/
function initHLSStream() {
if (!window.Hls) {
console.error('HLS.js non caricato');
return false;
}
if (Hls.isSupported()) {
console.log('HLS supportato - uso HLS.js');
hlsInstance = new Hls({
debug: false,
enableWorker: true,
lowLatencyMode: false, // Disabilitato LL per migliorare stabilità ABR su flussi standard
backBufferLength: 90
});
hlsInstance.loadSource(streamHLS);
hlsInstance.attachMedia(audioPlayer);
hlsInstance.on(Hls.Events.MANIFEST_PARSED, function (event, data) {
console.log('Manifest HLS caricato');
console.log('Livelli trovati:', data.levels.length, data.levels);
if (data.levels.length > 1) {
console.log('Multi-bitrate disponibile. Auto-switch abilitato.');
} else {
console.warn('ATTENZIONE: Trovato solo 1 livello di qualità. ABR non possibile.');
}
audioPlayer.play().then(() => {
console.log('Riproduzione HLS avviata');
updatePlayState(true);
}).catch(err => {
console.error('Errore avvio riproduzione HLS:', err);
tryFallbackStream();
});
});
hlsInstance.on(Hls.Events.LEVEL_SWITCHED, function (event, data) {
console.log('HLS Quality: Switched to level', data.level);
});
hlsInstance.on(Hls.Events.ERROR, function (event, data) {
console.error('Errore HLS:', data.type, data.details);
if (data.fatal) {
switch (data.type) {
case Hls.ErrorTypes.NETWORK_ERROR:
console.log('Errore di rete, tentativo fallback...');
tryFallbackStream();
break;
case Hls.ErrorTypes.MEDIA_ERROR:
console.log('Errore media, tentativo recupero...');
hlsInstance.recoverMediaError();
break;
default:
console.log('Errore fatale, tentativo fallback...');
tryFallbackStream();
break;
}
}
});
return true;
} else if (audioPlayer.canPlayType('application/vnd.apple.mpegurl')) {
console.log('HLS nativo supportato');
audioPlayer.src = streamHLS;
audioPlayer.play().then(() => {
console.log('Riproduzione HLS nativa avviata');
updatePlayState(true);
}).catch(err => {
console.error('Errore HLS nativo:', err);
tryFallbackStream();
});
return true;
}
return false;
}
/**
* Usa stream diretto come fallback
*/
function tryFallbackStream() {
console.log('Tentativo stream fallback:', streamFallback);
if (hlsInstance) {
hlsInstance.destroy();
hlsInstance = null;
}
streamType = 'direct';
audioPlayer.src = streamFallback;
audioPlayer.load();
audioPlayer.play().then(() => {
console.log('Riproduzione stream diretto avviata');
updatePlayState(true);
}).catch(err => {
console.error('Errore stream diretto:', err);
updatePlayState(false);
if (playerStatus) {
playerStatus.textContent = 'Errore di riproduzione';
}
});
}
/**
* Aggiorna stato visuale play/pause
*/
function updatePlayState(playing) {
isPlaying = playing;
if (playing) {
playIcon.style.display = 'none';
pauseIcon.style.display = 'inline-block';
if (playerStatus) {
playerStatus.textContent = 'In riproduzione...';
}
} else {
playIcon.style.display = 'inline-block';
pauseIcon.style.display = 'none';
if (playerStatus) {
playerStatus.textContent = stationSlogan || 'In pausa';
}
}
}
/**
* Gestione click play/pause
*/
playPauseBtn.addEventListener('click', function () {
if (!audioPlayer) return;
if (isPlaying) {
// Pausa
audioPlayer.pause();
updatePlayState(false);
console.log('Riproduzione in pausa');
} else {
// Play
if (!audioPlayer.src && !hlsInstance) {
// Prima riproduzione - inizializza lo stream
if (streamHLS && !initHLSStream()) {
// Se HLS fallisce, usa direttamente il fallback
tryFallbackStream();
}
} else {
// Riprendi riproduzione
audioPlayer.play().then(() => {
updatePlayState(true);
console.log('Riproduzione ripresa');
}).catch(err => {
console.error('Errore ripresa riproduzione:', err);
updatePlayState(false);
});
}
}
});
/**
* Event listeners audio player
*/
audioPlayer.addEventListener('play', function () {
updatePlayState(true);
});
audioPlayer.addEventListener('pause', function () {
updatePlayState(false);
});
audioPlayer.addEventListener('error', function (e) {
console.error('Errore audio player:', e);
updatePlayState(false);
if (playerStatus) {
playerStatus.textContent = 'Errore di riproduzione';
}
});
audioPlayer.addEventListener('waiting', function () {
if (playerStatus) {
playerStatus.textContent = 'Buffering...';
}
});
audioPlayer.addEventListener('playing', function () {
if (playerStatus) {
playerStatus.textContent = 'In riproduzione...';
}
});
// Setup Media Session
setupMediaSession(stationName, stationSlogan, stationLogo, playIcon, pauseIcon);
console.log('Player audio inizializzato');
}
/**
* 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();
}