diff --git a/data/changelog.xml b/data/changelog.xml index cbe8276..73cbdac 100644 --- a/data/changelog.xml +++ b/data/changelog.xml @@ -3,7 +3,7 @@ - 2.1.2 (c) + 2.1.2 (d) Correzione e bugfix di problematiche varie. diff --git a/js/app.js b/js/app.js index fb9ebb2..8c6831c 100644 --- a/js/app.js +++ b/js/app.js @@ -309,282 +309,173 @@ document.addEventListener('DOMContentLoaded', function () { */ function initializePlayer() { - console.log('Inizializzazione player audio...'); + console.log('Inizializzazione player audio (v2.0 - Hybrid Strategy)...'); const playPauseBtn = document.getElementById('playPauseBtn'); const playIcon = document.querySelector('.play-icon'); const pauseIcon = document.querySelector('.pause-icon'); const playerStatus = document.getElementById('playerStatus'); - const artistElement = document.getElementById('artist'); - if (!playPauseBtn) { - console.error('Pulsante play/pause non trovato'); - return; - } + if (!playPauseBtn) return; - // Pulisci il player precedente + // 1. Cleanup Preventivo cleanupPlayer(); - // Ottieni il nuovo audio player dal DOM + // 2. Setup Elementi audioPlayer = document.getElementById('hlsAudioPlayer'); - - if (!audioPlayer) { - console.error('Elemento audio non trovato'); - return; - } + if (!audioPlayer) 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' + + // Configurazione HLS.js Ottimizzata per Qualità + const hlsConfig = { + debug: false, + enableWorker: true, + lowLatencyMode: false, // Disabilitato per stabilità e buffer migliore + backBufferLength: 90, + maxBufferLength: 30, // Aumentato buffer (default 30s) + startLevel: -1, // Auto start + xhrSetup: function (xhr, url) { + xhr.withCredentials = false; // Fix CORS issues sometimes + } + }; /** - * Inizializza HLS stream + * Logica di Selezione Player + * Priorità: HLS.js (PC/Android) > Nativo (iOS) > Fallback (Direct Stream) */ - function initHLSStream() { - if (!window.Hls) { - console.error('HLS.js non caricato'); - return false; - } - + function initStream() { + // STRATEGIA 1: HLS.js (Preferita per PC e Android) 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 - }); + console.log('Strategy: HLS.js (High Quality Control)'); + hlsInstance = new Hls(hlsConfig); hlsInstance.loadSource(streamHLS); hlsInstance.attachMedia(audioPlayer); hlsInstance.on(Hls.Events.MANIFEST_PARSED, function (event, data) { - console.log('Manifest HLS caricato'); - console.log('Livelli trovati:', data.levels.length, data.levels); + console.log(`HLS Manifest Loaded. Levels: ${data.levels.length}`); + // Se ci sono più livelli, prova a partire aggressivo se non specificato if (data.levels.length > 1) { - console.log('Multi-bitrate disponibile. Auto-switch abilitato.'); - - // TENTATIVO FIX QUALITY: Forza il livello massimo all'avvio - // Questo bypassa la stima iniziale conservativa della banda - const maxLevel = data.levels.length - 1; - console.log(`Forcing start level to max: ${maxLevel} (${data.levels[maxLevel].height}p / ${data.levels[maxLevel].bitrate})`); - hlsInstance.nextLevel = maxLevel; - // Nota: startLevel funziona solo prima di startLoad, ma nextLevel forza il prossimo frammento - } else { - console.warn('ATTENZIONE: Trovato solo 1 livello di qualità. ABR non possibile.'); + // Opzionale: Logica custom startLevel se necessario + // hlsInstance.startLevel = data.levels.length - 1; } - 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); + attemptPlay(); }); hlsInstance.on(Hls.Events.ERROR, function (event, data) { - console.error('Errore HLS:', data.type, data.details); if (data.fatal) { + console.warn('HLS Fatal Error:', data.type); switch (data.type) { case Hls.ErrorTypes.NETWORK_ERROR: - console.log('Errore di rete, tentativo fallback...'); - tryFallbackStream(); + hlsInstance.startLoad(); break; case Hls.ErrorTypes.MEDIA_ERROR: - console.log('Errore media, tentativo recupero...'); hlsInstance.recoverMediaError(); break; default: - console.log('Errore fatale, tentativo fallback...'); - tryFallbackStream(); + fallbackToDirectStream(); break; } } }); - return true; - } else if (audioPlayer.canPlayType('application/vnd.apple.mpegurl')) { - console.log('HLS nativo supportato'); - - // Visual Debugging per Mobile - if (playerStatus) { - playerStatus.textContent = 'Caricamento HLS Nativo...'; - } - - audioPlayer.src = streamHLS; - audioPlayer.play().then(() => { - console.log('Riproduzione HLS nativa avviata'); - updatePlayState(true); - if (playerStatus) playerStatus.textContent += ' (Native)'; // Debug visivo - }).catch(err => { - console.error('Errore HLS nativo:', err); - tryFallbackStream(); - }); - return true; + return; } - return false; + // STRATEGIA 2: Nativo (Obbligatorio per iOS) + if (audioPlayer.canPlayType('application/vnd.apple.mpegurl')) { + console.log('Strategy: Native HLS (iOS/Safari)'); + audioPlayer.src = streamHLS; + audioPlayer.preload = 'auto'; // Suggerisce al browser di caricare dati + + // Hack per iOS: Alcune volte serve un tocco utente, gestito dal click play + attemptPlay(); + return; + } + + // STRATEGIA 3: Fallback (Stream MP3 diretto) + console.log('Strategy: Direct Fallback'); + fallbackToDirectStream(); } - /** - * Usa stream diretto come fallback - */ - function tryFallbackStream() { - console.log('Tentativo stream fallback:', streamFallback); - + function fallbackToDirectStream() { if (hlsInstance) { hlsInstance.destroy(); hlsInstance = null; } - - streamType = 'direct'; audioPlayer.src = streamFallback; - audioPlayer.load(); - - audioPlayer.play().then(() => { - console.log('Riproduzione stream diretto avviata'); - updatePlayState(true); - if (playerStatus) playerStatus.textContent += ' (Fallback)'; // Debug visivo - }).catch(err => { - console.error('Errore stream diretto:', err); - updatePlayState(false); - if (playerStatus) { - playerStatus.textContent = 'Errore di riproduzione'; - } - }); + attemptPlay(); } - /** - * 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'; - } - } + function attemptPlay() { + // Nota: chiamare .play() senza interazione utente fallirà. + // Questa funzione prepara lo stato. Il vero play avviene al click. + if (playerStatus) playerStatus.textContent = 'Pronto alla riproduzione'; } - /** - * 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); + function togglePlay() { + if (audioPlayer.paused) { + const playPromise = audioPlayer.play(); + if (playPromise !== undefined) { + playPromise.then(() => { + updateUI(true); + }).catch(error => { + console.error('Play prevented:', error); + // Se HLS fallisce al play (es. stale manifest), prova fallback + if (!audioPlayer.src.includes(streamFallback) && !hlsInstance) { + fallbackToDirectStream(); + audioPlayer.play().catch(e => console.error('Fallback failed:', e)); + } }); } - } - }); - - /** - * 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 (Event):', e); - - if (audioPlayer.error) { - const err = audioPlayer.error; - console.error('Dettagli MediaError:', { - code: err.code, - message: err.message, - readableCode: getMediaErrorMessage(err.code) - }); - - // Mostra errore specifico all'utente se possibile - if (playerStatus) { - playerStatus.textContent = 'Errore: ' + getMediaErrorMessage(err.code); - } } else { - if (playerStatus) { - playerStatus.textContent = 'Errore sconosciuto'; - } - } - - updatePlayState(false); - }); - - // Helper per decodificare errori media - function getMediaErrorMessage(code) { - switch (code) { - case 1: return 'MEDIA_ERR_ABORTED (Aborted by user)'; - case 2: return 'MEDIA_ERR_NETWORK (Network error)'; - case 3: return 'MEDIA_ERR_DECODE (Decoding error)'; - case 4: return 'MEDIA_ERR_SRC_NOT_SUPPORTED (Format not supported)'; - default: return 'Unknown error code ' + code; + audioPlayer.pause(); + updateUI(false); } } - audioPlayer.addEventListener('waiting', function () { + function updateUI(playing) { + isPlaying = playing; + playIcon.style.display = playing ? 'none' : 'block'; + pauseIcon.style.display = playing ? 'block' : 'none'; if (playerStatus) { - playerStatus.textContent = 'Buffering...'; + playerStatus.textContent = playing ? 'In riproduzione' : 'In pausa'; } - }); + } - audioPlayer.addEventListener('playing', function () { - if (playerStatus) { - playerStatus.textContent = 'In riproduzione...'; + // Listeners + playPauseBtn.onclick = (e) => { + e.preventDefault(); + // Se non inizializzato (primo click), avvia stream + if (!audioPlayer.src && !hlsInstance) { + initStream(); + // Piccolo timeout per dare tempo al setup prima del play effettivo + setTimeout(togglePlay, 50); + } else { + togglePlay(); } - }); + }; + + audioPlayer.onplay = () => updateUI(true); + audioPlayer.onpause = () => updateUI(false); + + audioPlayer.onerror = (e) => { + console.error('Audio Error:', audioPlayer.error); + if (playerStatus) playerStatus.textContent = 'Errore riproduzione'; + updateUI(false); + }; // Setup Media Session - setupMediaSession(stationName, stationSlogan, stationLogo, playIcon, pauseIcon); + setupMediaSession(stationName, playPauseBtn.getAttribute('data-station-slogan'), playPauseBtn.getAttribute('data-station-logo'), playIcon, pauseIcon); - console.log('Player audio inizializzato'); + console.log('Player System Ready.'); } /**