vers. 2.1.1
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
# Abilita il rewrite engine
|
||||
RewriteEngine On
|
||||
|
||||
# Imposta la directory base (modifica se necessario)
|
||||
# Se l'app è in una sottocartella, usa: RewriteBase /nome_cartella/
|
||||
RewriteBase /newapp.rpigroup.it/
|
||||
|
||||
# Reindirizza richieste HTTP a HTTPS (opzionale, decommentare se necessario)
|
||||
# RewriteCond %{HTTPS} off
|
||||
# RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
|
||||
|
||||
# Non reindirizzare file e cartelle esistenti
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
|
||||
# Escludi file statici dal rewriting
|
||||
RewriteCond %{REQUEST_URI} !\.(css|js|jpg|jpeg|png|gif|svg|ico|xml|json|woff|woff2|ttf|eot)$ [NC]
|
||||
|
||||
# Reindirizza tutto a index.php mantenendo il path
|
||||
RewriteRule ^(.*)$ index.php/$1 [L,QSA]
|
||||
|
||||
# Impedisci l'accesso diretto a file sensibili
|
||||
<FilesMatch "^(config|\.htaccess|\.env)">
|
||||
Order Allow,Deny
|
||||
Deny from all
|
||||
</FilesMatch>
|
||||
|
||||
# Gestione MIME types
|
||||
<IfModule mod_mime.c>
|
||||
AddType application/javascript js
|
||||
AddType text/css css
|
||||
AddType image/svg+xml svg
|
||||
AddType application/vnd.ms-fontobject eot
|
||||
AddType font/ttf ttf
|
||||
AddType font/otf otf
|
||||
AddType font/woff woff
|
||||
AddType font/woff2 woff2
|
||||
</IfModule>
|
||||
|
||||
# Abilita compressione GZIP (opzionale)
|
||||
<IfModule mod_deflate.c>
|
||||
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript application/javascript application/json
|
||||
</IfModule>
|
||||
|
||||
# Cache control (opzionale - commentato perché hai lo script no-cache)
|
||||
# <IfModule mod_expires.c>
|
||||
# ExpiresActive On
|
||||
# ExpiresByType image/jpg "access plus 1 month"
|
||||
# ExpiresByType image/jpeg "access plus 1 month"
|
||||
# ExpiresByType image/gif "access plus 1 month"
|
||||
# ExpiresByType image/png "access plus 1 month"
|
||||
# ExpiresByType image/svg+xml "access plus 1 month"
|
||||
# ExpiresByType text/css "access plus 1 week"
|
||||
# ExpiresByType application/javascript "access plus 1 week"
|
||||
# </IfModule>
|
||||
|
||||
# Header no-cache per sviluppo (rimuovi in produzione se usi la cache)
|
||||
<IfModule mod_headers.c>
|
||||
<FilesMatch "\.(html|php)$">
|
||||
Header set Cache-Control "no-cache, no-store, must-revalidate"
|
||||
Header set Pragma "no-cache"
|
||||
Header set Expires "0"
|
||||
</FilesMatch>
|
||||
</IfModule>
|
||||
|
||||
# Sicurezza aggiuntiva
|
||||
<IfModule mod_headers.c>
|
||||
Header set X-Content-Type-Options "nosniff"
|
||||
Header set X-Frame-Options "SAMEORIGIN"
|
||||
Header set X-XSS-Protection "1; mode=block"
|
||||
</IfModule>
|
||||
|
||||
# Disabilita directory listing
|
||||
Options -Indexes
|
||||
|
||||
# Abilita follow symlinks (necessario per RewriteRule)
|
||||
Options +FollowSymLinks
|
||||
Binary file not shown.
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
header('Content-Type: text/html; charset=UTF-8');
|
||||
|
||||
// Verifica se è una richiesta AJAX
|
||||
$is_ajax = !empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest';
|
||||
|
||||
|
||||
Binary file not shown.
@@ -1,5 +1,8 @@
|
||||
<?php
|
||||
|
||||
header('Content-Type: text/html; charset=UTF-8');
|
||||
|
||||
|
||||
// Information App
|
||||
$title_site = "RPIGroup Play";
|
||||
$description_site = "Ascolta le radio del gruppo RPIGroup";
|
||||
|
||||
Binary file not shown.
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
header('Content-Type: text/html; charset=UTF-8');
|
||||
|
||||
// Determina il percorso base dell'applicazione
|
||||
$script_name = $_SERVER['SCRIPT_NAME'];
|
||||
$script_path = dirname($script_name);
|
||||
|
||||
Binary file not shown.
+56
-3
@@ -1,10 +1,27 @@
|
||||
<?php
|
||||
|
||||
header('Content-Type: text/html; charset=UTF-8');
|
||||
|
||||
// File: config/getPage.inc.php
|
||||
|
||||
// Whitelist delle pagine valide
|
||||
$validPages = ['home', 'radio', 'tv', 'play', 'playtv', 'page'];
|
||||
$validSubPages = ['about', 'contact', 'copyright', 'addradio', 'termini-condizioni', 'policy-privacy', 'changelog'];
|
||||
|
||||
// Rileva se l'utente sta usando un dispositivo mobile
|
||||
function isMobile() {
|
||||
return preg_match("/(android|avantgo|blackberry|bolt|boost|cricket|docomo|fone|hiptop|mini|mobi|palm|phone|pie|tablet|up\.browser|up\.link|webos|wos)/i", $_SERVER["HTTP_USER_AGENT"]);
|
||||
}
|
||||
|
||||
// Funzione per sanitizzare l'input
|
||||
function sanitizePageInput($input) {
|
||||
// Rimuovi caratteri pericolosi
|
||||
$input = preg_replace('/[^a-zA-Z0-9\-_]/', '', $input);
|
||||
// Previeni path traversal
|
||||
$input = str_replace(['..', '/', '\\'], '', $input);
|
||||
return $input;
|
||||
}
|
||||
|
||||
// Recupera l'URL richiesto
|
||||
$request_uri = $_SERVER['REQUEST_URI'];
|
||||
$path = substr(urldecode($request_uri), strlen($base_path));
|
||||
@@ -23,9 +40,45 @@ if (isset($path_parts[0]) && $path_parts[0] == 'index.php') {
|
||||
array_shift($path_parts);
|
||||
}
|
||||
|
||||
// Determina la pagina da mostrare in base all'URL
|
||||
$page = isset($path_parts[0]) && !empty($path_parts[0]) ? $path_parts[0] : 'home';
|
||||
$param = isset($path_parts[1]) && !empty($path_parts[1]) ? $path_parts[1] : '';
|
||||
// Determina la pagina da mostrare in base all'URL con validazione
|
||||
$page = 'home'; // Default sicuro
|
||||
$param = '';
|
||||
|
||||
if (isset($path_parts[0]) && !empty($path_parts[0])) {
|
||||
$requestedPage = sanitizePageInput($path_parts[0]);
|
||||
|
||||
// Verifica se la pagina è nella whitelist
|
||||
if (in_array($requestedPage, $validPages)) {
|
||||
$page = $requestedPage;
|
||||
} else {
|
||||
// Pagina non valida, redirect a 404
|
||||
$page = 'home';
|
||||
error_log("Tentativo di accesso a pagina non valida: " . $path_parts[0]);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($path_parts[1]) && !empty($path_parts[1])) {
|
||||
$requestedParam = sanitizePageInput($path_parts[1]);
|
||||
|
||||
// Validazione specifica per tipo di pagina
|
||||
if ($page === 'play' || $page === 'playtv') {
|
||||
// Per play/playtv, il parametro deve essere un numero
|
||||
if (ctype_digit($requestedParam)) {
|
||||
$param = $requestedParam;
|
||||
} else {
|
||||
error_log("ID stazione non valido: " . $path_parts[1]);
|
||||
$page = 'home';
|
||||
}
|
||||
} elseif ($page === 'page') {
|
||||
// Per page, il parametro deve essere nella whitelist
|
||||
if (in_array($requestedParam, $validSubPages)) {
|
||||
$param = $requestedParam;
|
||||
} else {
|
||||
error_log("Sottopagina non valida: " . $path_parts[1]);
|
||||
$page = 'home';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Debug (rimuovi in produzione)
|
||||
error_log("Page: $page, Param: $param, Path: $path");
|
||||
Binary file not shown.
+178
-30
@@ -1,54 +1,202 @@
|
||||
<?php
|
||||
|
||||
// Funzione per caricare il file XML delle radio
|
||||
/**
|
||||
* Cache XML in memoria per la durata della richiesta
|
||||
* Previene caricamenti multipli dello stesso file
|
||||
*/
|
||||
class StationCache {
|
||||
private static $radioXML = null;
|
||||
private static $tvXML = null;
|
||||
private static $changelogXML = null;
|
||||
|
||||
/**
|
||||
* Carica e cachea il file XML delle radio
|
||||
*/
|
||||
public static function getRadioXML() {
|
||||
if (self::$radioXML === null) {
|
||||
$xmlPath = './data/radio.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::$radioXML = simplexml_load_file($xmlPath);
|
||||
|
||||
if (self::$radioXML === false) {
|
||||
$errors = libxml_get_errors();
|
||||
foreach ($errors as $error) {
|
||||
error_log("Errore XML radio.xml: " . trim($error->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");
|
||||
return null;
|
||||
}
|
||||
foreach ($xml->station as $station) {
|
||||
if ((int)$station->id === $id) {
|
||||
return $station;
|
||||
}
|
||||
}
|
||||
error_log("Impossibile caricare XML radio per ID: $id");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Funzione per caricare il file XML delle TV
|
||||
if (!isset($xml->station)) {
|
||||
error_log("Nessuna stazione trovata nel file XML");
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ($xml->station as $station) {
|
||||
if ((int)$station->id === (int)$id) {
|
||||
return $station;
|
||||
}
|
||||
}
|
||||
|
||||
error_log("Stazione radio non trovata con ID: $id");
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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");
|
||||
return null;
|
||||
}
|
||||
foreach ($xml->station as $station) {
|
||||
if ((int)$station->id === $id) {
|
||||
return $station;
|
||||
}
|
||||
}
|
||||
error_log("Impossibile caricare XML TV per 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];
|
||||
if (!isset($xml->station)) {
|
||||
error_log("Nessuna stazione TV trovata nel file XML");
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ($xml->station as $station) {
|
||||
if ((int)$station->id === (int)$id) {
|
||||
return $station;
|
||||
}
|
||||
}
|
||||
|
||||
error_log("Stazione TV non trovata con ID: $id");
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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");
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -2,6 +2,23 @@
|
||||
|
||||
<changelog>
|
||||
|
||||
<version>
|
||||
<number>2.1.1</number>
|
||||
<logs>
|
||||
<log>Corretti alcuni bug che impedivano l'accesso al player dal link esterno</log>
|
||||
<log>Correzione e bugfix di problematiche varie.</log>
|
||||
</logs>
|
||||
</version>
|
||||
|
||||
<version>
|
||||
<number>2.1.0</number>
|
||||
<logs>
|
||||
<log>Risoluzione dei problemi minori presenti nel codice, che causava problemi nella navigazione in app</log>
|
||||
<log>Risoluzione dei problemi minori presenti nel codice, che causava problemi di riproduzione audio al player</log>
|
||||
<log>Correzione e bugfix di problematiche varie.</log>
|
||||
</logs>
|
||||
</version>
|
||||
|
||||
<version>
|
||||
<number>2.0.4</number>
|
||||
<logs>
|
||||
|
||||
Binary file not shown.
+2
-2
@@ -35,7 +35,7 @@
|
||||
<contentplayer></contentplayer>
|
||||
</station>
|
||||
|
||||
<station>
|
||||
<!-- <station>
|
||||
<id>4</id>
|
||||
<name>RC105 Christmas - Eboli</name>
|
||||
<slogan>La musica di Natale, in giro per Eboli</slogan>
|
||||
@@ -44,7 +44,7 @@
|
||||
<stream>https://srvone.radio.asvhosting.com/listen/rc105_christmas/radio.aac</stream>
|
||||
<streamhls>https://srvone.radio.asvhosting.com/hls/rc105_christmas/live.m3u8</streamhls>
|
||||
<contentplayer></contentplayer>
|
||||
</station>
|
||||
</station> -->
|
||||
|
||||
<station>
|
||||
<id>5</id>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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';
|
||||
|
||||
Binary file not shown.
@@ -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,116 +17,233 @@ 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);
|
||||
}
|
||||
|
||||
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';
|
||||
}
|
||||
|
||||
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 = '<div class="content-page"><h2>Errore</h2><p>Impossibile caricare la pagina. Codice errore: ' + xhr.status + '</p></div>';
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onerror = function() {
|
||||
contentContainer.innerHTML = '<div class="content-page"><h2>Errore di connessione</h2><p>Controlla la tua connessione internet.</p></div>';
|
||||
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');
|
||||
};
|
||||
|
||||
xhr.send();
|
||||
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');
|
||||
} else if (page.startsWith('page/')) {
|
||||
if (linkPage === page) {
|
||||
console.log('✓ Match playtv/ -> tv');
|
||||
} else if (page.startsWith('page/') && linkPage === page) {
|
||||
link.classList.add('active');
|
||||
}
|
||||
console.log('✓ Match page/');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 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 +258,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 +285,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,214 +304,252 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inizializza il player audio con gestione HLS migliorata
|
||||
*/
|
||||
|
||||
function initializePlayer() {
|
||||
console.log('Inizializzazione player...');
|
||||
console.log('Inizializzazione player audio...');
|
||||
|
||||
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 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');
|
||||
const statusElement = document.getElementById('playerStatus');
|
||||
|
||||
console.log('Station:', stationName);
|
||||
console.log('HLS URL:', hlsUrl);
|
||||
console.log('Fallback URL:', fallbackUrl);
|
||||
console.log('Stream HLS:', streamHLS);
|
||||
console.log('Stream Fallback:', streamFallback);
|
||||
console.log('Stazione:', stationName);
|
||||
|
||||
// Setup Media Session all'inizio passando anche le icone
|
||||
setupMediaSession(stationName, stationSlogan, stationLogo, playIcon, pauseIcon);
|
||||
let isPlaying = false;
|
||||
let streamType = 'hls'; // 'hls' o 'direct'
|
||||
|
||||
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');
|
||||
/**
|
||||
* Inizializza HLS stream
|
||||
*/
|
||||
function initHLSStream() {
|
||||
if (!window.Hls) {
|
||||
console.error('HLS.js non caricato');
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log('Uso HLS.js');
|
||||
updateStatus('Caricamento...');
|
||||
if (Hls.isSupported()) {
|
||||
console.log('HLS supportato - uso HLS.js');
|
||||
|
||||
hlsInstance = new Hls({
|
||||
debug: false,
|
||||
enableWorker: true,
|
||||
lowLatencyMode: true
|
||||
lowLatencyMode: true,
|
||||
backBufferLength: 90
|
||||
});
|
||||
|
||||
hlsInstance.loadSource(hlsUrl);
|
||||
hlsInstance.loadSource(streamHLS);
|
||||
hlsInstance.attachMedia(audioPlayer);
|
||||
|
||||
hlsInstance.on(Hls.Events.MANIFEST_PARSED, function () {
|
||||
console.log('HLS manifest caricato');
|
||||
updateStatus('Pronto');
|
||||
console.log('Manifest HLS caricato');
|
||||
audioPlayer.play().then(() => {
|
||||
console.log('Riproduzione HLS avviata');
|
||||
updatePlayState(true);
|
||||
}).catch(err => {
|
||||
console.error('Errore avvio riproduzione HLS:', err);
|
||||
tryFallbackStream();
|
||||
});
|
||||
});
|
||||
|
||||
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();
|
||||
console.log('Errore di rete, tentativo fallback...');
|
||||
tryFallbackStream();
|
||||
break;
|
||||
case Hls.ErrorTypes.MEDIA_ERROR:
|
||||
console.log('Errore media, riprovo...');
|
||||
console.log('Errore media, tentativo recupero...');
|
||||
hlsInstance.recoverMediaError();
|
||||
break;
|
||||
default:
|
||||
console.error('Errore fatale, uso fallback');
|
||||
hlsInstance.destroy();
|
||||
hlsInstance = null;
|
||||
useFallback();
|
||||
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;
|
||||
}
|
||||
|
||||
function useFallback() {
|
||||
console.log('Uso fallback:', fallbackUrl);
|
||||
if (fallbackUrl) {
|
||||
audioPlayer.src = fallbackUrl;
|
||||
audioPlayer.load();
|
||||
updateStatus('Pronto (MP3)');
|
||||
} else {
|
||||
updateStatus('Stream non disponibile', true);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hlsUrl) {
|
||||
if (canPlayHLS()) {
|
||||
console.log('Uso HLS nativo (Safari)');
|
||||
audioPlayer.src = hlsUrl;
|
||||
updateStatus('Pronto');
|
||||
} else if (!setupHLS()) {
|
||||
useFallback();
|
||||
}
|
||||
} else {
|
||||
useFallback();
|
||||
}
|
||||
/**
|
||||
* Usa stream diretto come fallback
|
||||
*/
|
||||
function tryFallbackStream() {
|
||||
console.log('Tentativo stream fallback:', streamFallback);
|
||||
|
||||
// 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...');
|
||||
if (hlsInstance) {
|
||||
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.');
|
||||
}
|
||||
|
||||
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 {
|
||||
audioPlayer.pause();
|
||||
playIcon.style.display = 'block';
|
||||
playIcon.style.display = 'inline-block';
|
||||
pauseIcon.style.display = 'none';
|
||||
updateStatus('In pausa');
|
||||
updateMediaSessionPlaybackState('paused');
|
||||
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 con Media Session updates e sincronizzazione UI
|
||||
/**
|
||||
* Event listeners audio player
|
||||
*/
|
||||
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';
|
||||
updatePlayState(true);
|
||||
});
|
||||
|
||||
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';
|
||||
updatePlayState(false);
|
||||
});
|
||||
|
||||
audioPlayer.addEventListener('ended', function() {
|
||||
playIcon.style.display = 'block';
|
||||
pauseIcon.style.display = 'none';
|
||||
updateStatus('Terminato');
|
||||
updateMediaSessionPlaybackState('paused');
|
||||
audioPlayer.addEventListener('error', function (e) {
|
||||
console.error('Errore audio player:', e);
|
||||
updatePlayState(false);
|
||||
if (playerStatus) {
|
||||
playerStatus.textContent = 'Errore di riproduzione';
|
||||
}
|
||||
});
|
||||
|
||||
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', function () {
|
||||
if (playerStatus) {
|
||||
playerStatus.textContent = 'Buffering...';
|
||||
}
|
||||
});
|
||||
|
||||
audioPlayer.addEventListener('waiting', () => updateStatus('Buffering...'));
|
||||
audioPlayer.addEventListener('playing', () => updateStatus('In riproduzione'));
|
||||
audioPlayer.addEventListener('playing', function () {
|
||||
if (playerStatus) {
|
||||
playerStatus.textContent = 'In riproduzione...';
|
||||
}
|
||||
});
|
||||
|
||||
console.log('Player inizializzato con Media Session');
|
||||
// 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) return;
|
||||
if (!contactForm) {
|
||||
console.log('Form contatti non trovato');
|
||||
return;
|
||||
}
|
||||
|
||||
contactForm.addEventListener('submit', function (e) {
|
||||
e.preventDefault();
|
||||
@@ -392,53 +557,78 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
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 => {
|
||||
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 +637,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 +684,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 +706,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 +798,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 +819,6 @@ function tryLockOrientation() {
|
||||
lockScreenOrientation();
|
||||
}
|
||||
|
||||
// Prova anche quando entriamo in fullscreen
|
||||
document.addEventListener('fullscreenchange', () => {
|
||||
if (document.fullscreenElement) {
|
||||
lockScreenOrientation();
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,3 +1,5 @@
|
||||
<?php header('Content-Type: text/html; charset=UTF-8'); ?>
|
||||
|
||||
<div id="proradio-secondary-header" class="proradio-secondaryhead proradio-primary" style="border-bottom: solid 3px #3849a8;">
|
||||
|
||||
<style>
|
||||
@@ -59,6 +61,7 @@
|
||||
<a href="https://www.radiocitta105.it" target="_blank"><span class="link">Radio Città 105</span></a>
|
||||
<a href="https://www.radiodiffusionelibera.com" target="_blank"><span class="link">Radio DiffusioneLibera</span></a>
|
||||
<a href="https://www.co-municare.it" target="_blank"><span class="link">Co-Municare.it</span></a>
|
||||
<a href="https://www.ineboli.it" target="_blank"><span class="link">InEboli</span></a>
|
||||
<a href="https://app.rpigroup.it" target="_blank"><span class="link active">RPIGroup Play</span></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Binary file not shown.
+6
-1
@@ -1,3 +1,8 @@
|
||||
<?php
|
||||
// Debug: verifica valori delle variabili (rimuovi dopo aver risolto)
|
||||
// echo "<!-- DEBUG - Page: " . htmlspecialchars($page) . " | Param: " . htmlspecialchars($param) . " -->\n";
|
||||
?>
|
||||
|
||||
<div class="header">
|
||||
<div class="logo-section">
|
||||
<img src="<?=$base_path?>/img/RpiGroupPlayWHITE.png" alt="Logo">
|
||||
@@ -94,5 +99,5 @@
|
||||
<a href="<?php echo $base_path; ?>/page/policy-privacy" data-page="page/policy-privacy" class="navLink">Policy Privacy</a> •
|
||||
<a href="<?php echo $base_path; ?>/page/changelog" data-page="page/changelog" class="navLink">Changelog</a>
|
||||
</div>
|
||||
<div class="copyright-section" <?php if($is_mobile){ ?> style="padding: 10px 0 25px;" <? } ?>>© 2025 RPIGroup • Versione: <?php echo $version_app; ?></div>
|
||||
<div class="copyright-section" <?php if($is_mobile){ ?> style="padding: 10px 0 25px;" <?php } ?>>© 2025 RPIGroup • Versione: <?php echo $version_app; ?></div>
|
||||
</div>
|
||||
Binary file not shown.
@@ -1,3 +1,5 @@
|
||||
<?php header('Content-Type: text/html; charset=UTF-8'); ?>
|
||||
|
||||
<h1 class="titlePage">Pagina non trovata</h1>
|
||||
|
||||
<p class="text-center">La pagina che hai provato a cercare non esiste o non è disponibile.</p>
|
||||
|
||||
Binary file not shown.
@@ -1,3 +1,5 @@
|
||||
<?php header('Content-Type: text/html; charset=UTF-8'); ?>
|
||||
|
||||
<h1 class="titlePage">Come funziona RPIGroup Play?</h1>
|
||||
|
||||
<hr>
|
||||
|
||||
Binary file not shown.
@@ -1,3 +1,5 @@
|
||||
<?php header('Content-Type: text/html; charset=UTF-8'); ?>
|
||||
|
||||
<h1 class="titlePage">Aggiungi la tua radio</h1>
|
||||
|
||||
<hr>
|
||||
|
||||
Binary file not shown.
@@ -1,13 +1,31 @@
|
||||
<?php header('Content-Type: text/html; charset=UTF-8'); ?>
|
||||
|
||||
<h1 class="titlePage">Changelog</h1>
|
||||
<p class="subtitlePage">Visualizza tutti gli ricevuti</p>
|
||||
<p class="subtitlePage">Visualizza tutti gli aggiornamenti ricevuti</p>
|
||||
|
||||
<?php
|
||||
// Verifica che $changelog sia stato caricato correttamente in getStation.inc.php
|
||||
if (isset($changelog) && $changelog !== false && isset($changelog->version)) {
|
||||
// Itera attraverso le versioni
|
||||
foreach($changelog->version as $version) {
|
||||
echo "<hr>";
|
||||
echo "<p class=\"changelogTitle\">Versione ".$version->number."</p>";
|
||||
echo "<p class=\"changelogTitle\">Versione " . htmlspecialchars((string)$version->number) . "</p>";
|
||||
|
||||
// Verifica che esistano i log prima di iterare
|
||||
if (isset($version->logs) && isset($version->logs->log)) {
|
||||
foreach($version->logs->log as $log_print) {
|
||||
echo "<p class=\"changelogList\">• ".$log_print."</p>";
|
||||
echo "<p class=\"changelogList\">• " . htmlspecialchars((string)$log_print) . "</p>";
|
||||
}
|
||||
} else {
|
||||
echo "<p class=\"changelogList\" style=\"font-style: italic; color: #999;\">Nessun dettaglio disponibile per questa versione.</p>";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Messaggio di errore se il changelog non è disponibile
|
||||
echo "<hr>";
|
||||
echo "<div style=\"padding: 20px; background: #fff3cd; border: 1px solid #ffc107; border-radius: 5px; margin: 20px 0;\">";
|
||||
echo "<p style=\"margin: 0; color: #856404;\"><strong>Attenzione:</strong> Impossibile caricare il changelog. Il file potrebbe essere mancante o corrotto.</p>";
|
||||
echo "</div>";
|
||||
}
|
||||
?>
|
||||
|
||||
|
||||
Binary file not shown.
@@ -1,3 +1,5 @@
|
||||
<?php header('Content-Type: text/html; charset=UTF-8'); ?>
|
||||
|
||||
<!-- <div class="content-page">
|
||||
<h2>Contatti</h2>
|
||||
<div class="content-box">
|
||||
|
||||
Binary file not shown.
@@ -1,3 +1,5 @@
|
||||
<?php header('Content-Type: text/html; charset=UTF-8'); ?>
|
||||
|
||||
<h1 class="titlePage">Diritti d'Autore</h1>
|
||||
<p class="subtitlePage">Come funziona e chi lo gestisce</p>
|
||||
|
||||
|
||||
Binary file not shown.
@@ -1,3 +1,5 @@
|
||||
<?php header('Content-Type: text/html; charset=UTF-8'); ?>
|
||||
|
||||
<h1 class="titlePage">Benvenuto</h1>
|
||||
<p class="subtitlePage">nella nuova RPIGroup Play</p>
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,3 +1,5 @@
|
||||
<?php header('Content-Type: text/html; charset=UTF-8'); ?>
|
||||
|
||||
<h1 class="titlePage">Policy Privacy</h1>
|
||||
<p class="subtitlePage">Ultimo aggiornamento: 17/11/2025</p>
|
||||
|
||||
|
||||
Binary file not shown.
@@ -1,3 +1,5 @@
|
||||
<?php header('Content-Type: text/html; charset=UTF-8'); ?>
|
||||
|
||||
<?php
|
||||
// views/home.php - Vista della pagina principale
|
||||
$stations = loadRadioStations();
|
||||
|
||||
Binary file not shown.
@@ -1,3 +1,5 @@
|
||||
<?php header('Content-Type: text/html; charset=UTF-8'); ?>
|
||||
|
||||
<h1 class="titlePage">Termini & Condizioni</h1>
|
||||
<p class="subtitlePage">Ultimo aggiornamento: 17/11/2025</p>
|
||||
|
||||
|
||||
Binary file not shown.
@@ -1,3 +1,5 @@
|
||||
<?php header('Content-Type: text/html; charset=UTF-8'); ?>
|
||||
|
||||
<h1 class="titlePage">TV</h1>
|
||||
<p class="subtitlePage">Guarda in streaming <b>RC105 TV</b></p>
|
||||
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
// File: process_contact.php
|
||||
|
||||
// Imposta header per JSON
|
||||
header('Content-Type: application/json');
|
||||
|
||||
// Verifica se il modulo è stato inviato
|
||||
if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
||||
// Raccolta e pulizia dei dati
|
||||
$name = filter_input(INPUT_POST, 'name', FILTER_SANITIZE_STRING);
|
||||
$email = filter_input(INPUT_POST, 'email', FILTER_SANITIZE_EMAIL);
|
||||
$subject = filter_input(INPUT_POST, 'subject', FILTER_SANITIZE_STRING);
|
||||
$message = filter_input(INPUT_POST, 'message', FILTER_SANITIZE_STRING);
|
||||
|
||||
// Validazione dei dati
|
||||
$errors = [];
|
||||
|
||||
if (empty($name)) {
|
||||
$errors[] = "Il nome è richiesto";
|
||||
}
|
||||
|
||||
if (empty($email) || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
$errors[] = "Email non valida";
|
||||
}
|
||||
|
||||
if (empty($subject)) {
|
||||
$errors[] = "L'oggetto è richiesto";
|
||||
}
|
||||
|
||||
if (empty($message)) {
|
||||
$errors[] = "Il messaggio è richiesto";
|
||||
}
|
||||
|
||||
// Se non ci sono errori, procedi con l'invio della mail
|
||||
if (empty($errors)) {
|
||||
// Destinatario
|
||||
$to = "info@tuoaggregatore.it";
|
||||
|
||||
// Intestazioni
|
||||
$headers = "From: $name <$email>" . "\r\n";
|
||||
$headers .= "Reply-To: $email" . "\r\n";
|
||||
$headers .= "X-Mailer: PHP/" . phpversion();
|
||||
|
||||
// Prova a inviare l'email
|
||||
$success = mail($to, $subject, $message, $headers);
|
||||
|
||||
if ($success) {
|
||||
echo json_encode(['success' => true, 'message' => 'Messaggio inviato con successo']);
|
||||
} else {
|
||||
echo json_encode(['success' => false, 'message' => 'Impossibile inviare il messaggio']);
|
||||
}
|
||||
} else {
|
||||
// Restituisci errori di validazione
|
||||
echo json_encode(['success' => false, 'message' => implode(', ', $errors)]);
|
||||
}
|
||||
} else {
|
||||
// Se qualcuno prova ad accedere direttamente a questa pagina
|
||||
echo json_encode(['success' => false, 'message' => 'Metodo non consentito']);
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,7 +1,4 @@
|
||||
|
||||
<div style="display:none">
|
||||
<p><a href="https://www.jennymcnieceflowers.com/">777slot</a></p>
|
||||
</div>
|
||||
<script src="<?php echo $base_path; ?>/js/app.js?v=<?=time()?>"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user