Primo Commit

This commit is contained in:
root
2026-01-03 11:59:31 +01:00
commit 719d750a7a
72 changed files with 14088 additions and 0 deletions

618
js/app.js Normal file
View File

@@ -0,0 +1,618 @@
document.addEventListener('DOMContentLoaded', function() {
const BASE_PATH = window.BASE_PATH || '';
let currentPage = getCurrentPageFromPath();
let audioPlayer = null;
let hlsInstance = null;
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');
script.onerror = () => console.error('Errore caricamento HLS.js');
document.head.appendChild(script);
}
function getCurrentPageFromPath() {
let path = window.location.pathname;
if (BASE_PATH && path.startsWith(BASE_PATH)) {
path = path.substring(BASE_PATH.length);
}
path = path.replace(/^\/|\/$/g, '');
return path || 'home';
}
function loadPage(page) {
const contentContainer = document.getElementById('content');
if (!contentContainer) return;
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');
xhr.onload = function() {
if (xhr.status === 200) {
contentContainer.innerHTML = xhr.responseText;
contentContainer.classList.remove('fade-out');
contentContainer.classList.add('fade-in');
const historyUrl = page === 'home' ? BASE_PATH + '/' : BASE_PATH + '/' + page;
history.pushState({page: page}, null, historyUrl);
currentPage = page;
updateActiveNavigation(page);
if (page.startsWith('play/')) {
initializePlayer();
}
if (page.startsWith('playtv/')) {
initializeTVPlayer();
}
attachLinkListeners();
if (page === 'page/contact') {
initializeContactForm();
}
window.scrollTo({
top: 0,
behavior: 'smooth'
});
} else {
contentContainer.innerHTML = '<div class="content-page"><h2>Errore</h2><p>Impossibile caricare la pagina. Codice errore: ' + xhr.status + '</p></div>';
contentContainer.classList.remove('fade-out');
contentContainer.classList.add('fade-in');
}
};
xhr.onerror = function() {
contentContainer.innerHTML = '<div class="content-page"><h2>Errore di connessione</h2><p>Controlla la tua connessione internet.</p></div>';
contentContainer.classList.remove('fade-out');
contentContainer.classList.add('fade-in');
};
xhr.send();
}, 200);
}
function updateActiveNavigation(page) {
const navLinks = document.querySelectorAll('.navLink');
navLinks.forEach(link => {
link.classList.remove('active');
const linkPage = link.getAttribute('data-page');
if (linkPage === page) {
link.classList.add('active');
} else if (page.startsWith('play/') && linkPage === 'radio') {
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');
}
}
});
}
// NUOVA FUNZIONE: Setup Media Session API
function setupMediaSession(stationName, stationSlogan, stationLogo, playIcon, pauseIcon) {
if (!('mediaSession' in navigator)) {
console.log('Media Session API non supportata');
return;
}
console.log('Setup Media Session:', stationName, stationSlogan);
// Imposta i metadati
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' }
]
});
// Gestisci i controlli del centro notifiche
navigator.mediaSession.setActionHandler('play', () => {
console.log('Media Session: Play richiesto dal centro notifiche');
if (audioPlayer) {
audioPlayer.play().then(() => {
// Aggiorna UI
if (playIcon && pauseIcon) {
playIcon.style.display = 'none';
pauseIcon.style.display = 'block';
}
});
}
});
navigator.mediaSession.setActionHandler('pause', () => {
console.log('Media Session: Pause richiesto dal centro notifiche');
if (audioPlayer) {
audioPlayer.pause();
// Aggiorna UI
if (playIcon && pauseIcon) {
playIcon.style.display = 'block';
pauseIcon.style.display = 'none';
}
}
});
// Opzionale: gestisci skip (per radio potrebbe non servire)
navigator.mediaSession.setActionHandler('seekbackward', null);
navigator.mediaSession.setActionHandler('seekforward', null);
navigator.mediaSession.setActionHandler('previoustrack', null);
navigator.mediaSession.setActionHandler('nexttrack', null);
}
// NUOVA FUNZIONE: Aggiorna playback state
function updateMediaSessionPlaybackState(state) {
if ('mediaSession' in navigator) {
navigator.mediaSession.playbackState = state;
console.log('Media Session playback state:', state);
}
}
function initializePlayer() {
console.log('Inizializzazione player...');
const playPauseBtn = document.getElementById('playPauseBtn');
if (!playPauseBtn) return;
const playIcon = document.querySelector('.play-icon');
const pauseIcon = document.querySelector('.pause-icon');
const hlsUrl = playPauseBtn.getAttribute('data-stream-hls');
const fallbackUrl = 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');
const statusElement = document.getElementById('playerStatus');
console.log('Station:', stationName);
console.log('HLS URL:', hlsUrl);
console.log('Fallback URL:', fallbackUrl);
// Setup Media Session all'inizio passando anche le icone
setupMediaSession(stationName, stationSlogan, stationLogo, playIcon, pauseIcon);
if (hlsInstance) {
hlsInstance.destroy();
hlsInstance = null;
}
if (audioPlayer) {
audioPlayer.pause();
audioPlayer = null;
}
audioPlayer = document.getElementById('hlsAudioPlayer');
if (!audioPlayer) {
audioPlayer = new Audio();
}
audioPlayer.preload = 'none';
function updateStatus(msg, isError = false) {
console.log('Status:', msg);
if (statusElement) {
statusElement.textContent = msg;
statusElement.className = 'station-status' + (isError ? ' error' : '');
}
}
function canPlayHLS() {
const video = document.createElement('video');
return video.canPlayType('application/vnd.apple.mpegurl') !== '';
}
function setupHLS() {
if (!window.Hls || !Hls.isSupported()) {
console.log('HLS.js non disponibile');
return false;
}
console.log('Uso HLS.js');
updateStatus('Caricamento...');
hlsInstance = new Hls({
enableWorker: true,
lowLatencyMode: true
});
hlsInstance.loadSource(hlsUrl);
hlsInstance.attachMedia(audioPlayer);
hlsInstance.on(Hls.Events.MANIFEST_PARSED, function() {
console.log('HLS manifest caricato');
updateStatus('Pronto');
});
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 rete, riprovo...');
hlsInstance.startLoad();
break;
case Hls.ErrorTypes.MEDIA_ERROR:
console.log('Errore media, riprovo...');
hlsInstance.recoverMediaError();
break;
default:
console.error('Errore fatale, uso fallback');
hlsInstance.destroy();
hlsInstance = null;
useFallback();
break;
}
}
});
return true;
}
function useFallback() {
console.log('Uso fallback:', fallbackUrl);
if (fallbackUrl) {
audioPlayer.src = fallbackUrl;
audioPlayer.load();
updateStatus('Pronto (MP3)');
} else {
updateStatus('Stream non disponibile', true);
}
}
if (hlsUrl) {
if (canPlayHLS()) {
console.log('Uso HLS nativo (Safari)');
audioPlayer.src = hlsUrl;
updateStatus('Pronto');
} else if (!setupHLS()) {
useFallback();
}
} else {
useFallback();
}
// Gestore pulsante play/pause
playPauseBtn.addEventListener('click', function() {
if (audioPlayer.paused) {
updateStatus('Connessione...');
audioPlayer.play()
.then(() => {
playIcon.style.display = 'none';
pauseIcon.style.display = 'block';
updateStatus('In riproduzione');
updateMediaSessionPlaybackState('playing');
})
.catch(error => {
console.error('Errore nella riproduzione:', error);
updateStatus('Errore riproduzione', true);
updateMediaSessionPlaybackState('paused');
if (hlsInstance && fallbackUrl) {
console.log('Tentativo con 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.');
});
}, 500);
} else {
alert('Impossibile riprodurre lo stream audio. Riprova più tardi.');
}
});
} else {
audioPlayer.pause();
playIcon.style.display = 'block';
pauseIcon.style.display = 'none';
updateStatus('In pausa');
updateMediaSessionPlaybackState('paused');
}
});
// Event listeners con Media Session updates e sincronizzazione UI
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';
});
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';
});
audioPlayer.addEventListener('ended', function() {
playIcon.style.display = 'block';
pauseIcon.style.display = 'none';
updateStatus('Terminato');
updateMediaSessionPlaybackState('paused');
});
audioPlayer.addEventListener('error', function() {
playIcon.style.display = 'block';
pauseIcon.style.display = 'none';
updateStatus('Errore stream', true);
updateMediaSessionPlaybackState('paused');
alert('Si è verificato un errore durante la riproduzione. Riprova più tardi.');
});
audioPlayer.addEventListener('waiting', () => updateStatus('Buffering...'));
audioPlayer.addEventListener('playing', () => updateStatus('In riproduzione'));
console.log('Player inizializzato con Media Session');
}
function initializeTVPlayer() {
console.log('TV Player inizializzato');
}
function initializeContactForm() {
const contactForm = document.getElementById('contactForm');
if (!contactForm) return;
contactForm.addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(contactForm);
const formResponse = document.getElementById('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';
contactForm.reset();
} else {
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';
}
} else {
formResponse.innerHTML = 'Si è verificato un errore durante l\'invio del messaggio.';
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);
});
}
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);
newLink.addEventListener('click', function(e) {
const href = this.getAttribute('href');
const target = this.getAttribute('target');
const page = this.getAttribute('data-page');
const isExternal = href && (
href.startsWith('http://') ||
href.startsWith('https://') ||
href.startsWith('//') ||
target === '_blank'
);
if (isExternal) {
console.log('Link esterno rilevato:', href);
e.preventDefault();
window.location.href = href;
return;
}
e.preventDefault();
e.stopPropagation();
console.log('Link cliccato, pagina:', page, 'currentPage:', currentPage);
if (page && page !== currentPage) {
if (audioPlayer && !audioPlayer.paused) {
audioPlayer.pause();
}
loadPage(page);
}
});
});
}
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);
});
}
}
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);
}
function setupStandaloneMode() {
const appContainer = document.querySelector('.container');
if (!appContainer) return;
if (window.opener && !window.opener.closed) {
appContainer.classList.add('standalone-app');
}
if (window.navigator.standalone || window.matchMedia('(display-mode: standalone)').matches) {
appContainer.classList.add('standalone-app');
}
}
function init() {
setupOpenAppButton();
if (document.querySelector('.container')) {
setupStandaloneMode();
attachLinkListeners();
setupHistoryNavigation();
initializeCurrentPage();
}
}
function initializeCurrentPage() {
console.log('Inizializzazione pagina corrente:', currentPage);
updateActiveNavigation(currentPage);
if (currentPage.startsWith('play/')) {
setTimeout(function() {
initializePlayer();
}, 100);
}
if (currentPage.startsWith('playtv/')) {
setTimeout(function() {
initializeTVPlayer();
}, 100);
}
if (currentPage === 'page/contact') {
setTimeout(function() {
initializeContactForm();
}, 100);
}
}
init();
});
// Funzione per bloccare l'orientamento (funziona solo in PWA/fullscreen)
function lockScreenOrientation() {
// Prova a bloccare l'orientamento con 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
if (screen.lockOrientation) {
screen.lockOrientation('portrait');
} else if (screen.mozLockOrientation) {
screen.mozLockOrientation('portrait');
} else if (screen.msLockOrientation) {
screen.msLockOrientation('portrait');
}
}
// Prova a bloccare quando l'app è in fullscreen/standalone
function tryLockOrientation() {
// Controlla se siamo in modalità standalone (PWA)
const isStandalone = window.navigator.standalone ||
window.matchMedia('(display-mode: standalone)').matches;
if (isStandalone) {
lockScreenOrientation();
}
// Prova anche quando entriamo in fullscreen
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();
}