aggiunto player hls v.2

This commit is contained in:
2026-01-28 23:13:46 +01:00
parent 008df364fe
commit 3c350b17ae
2 changed files with 97 additions and 206 deletions

View File

@@ -3,7 +3,7 @@
<changelog> <changelog>
<version> <version>
<number>2.1.2 (c)</number> <number>2.1.2 (d)</number>
<logs> <logs>
<log>Correzione e bugfix di problematiche varie.</log> <log>Correzione e bugfix di problematiche varie.</log>
</logs> </logs>

303
js/app.js
View File

@@ -309,282 +309,173 @@ document.addEventListener('DOMContentLoaded', function () {
*/ */
function initializePlayer() { function initializePlayer() {
console.log('Inizializzazione player audio...'); console.log('Inizializzazione player audio (v2.0 - Hybrid Strategy)...');
const playPauseBtn = document.getElementById('playPauseBtn'); const playPauseBtn = document.getElementById('playPauseBtn');
const playIcon = document.querySelector('.play-icon'); const playIcon = document.querySelector('.play-icon');
const pauseIcon = document.querySelector('.pause-icon'); const pauseIcon = document.querySelector('.pause-icon');
const playerStatus = document.getElementById('playerStatus'); const playerStatus = document.getElementById('playerStatus');
const artistElement = document.getElementById('artist');
if (!playPauseBtn) { if (!playPauseBtn) return;
console.error('Pulsante play/pause non trovato');
return;
}
// Pulisci il player precedente // 1. Cleanup Preventivo
cleanupPlayer(); cleanupPlayer();
// Ottieni il nuovo audio player dal DOM // 2. Setup Elementi
audioPlayer = document.getElementById('hlsAudioPlayer'); audioPlayer = document.getElementById('hlsAudioPlayer');
if (!audioPlayer) return;
if (!audioPlayer) {
console.error('Elemento audio non trovato');
return;
}
const streamHLS = playPauseBtn.getAttribute('data-stream-hls'); const streamHLS = playPauseBtn.getAttribute('data-stream-hls');
const streamFallback = playPauseBtn.getAttribute('data-stream-fallback'); const streamFallback = playPauseBtn.getAttribute('data-stream-fallback');
const stationName = playPauseBtn.getAttribute('data-station-name'); 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 isPlaying = false;
let streamType = 'hls'; // 'hls' o 'direct'
/** // Configurazione HLS.js Ottimizzata per Qualità
* Inizializza HLS stream const hlsConfig = {
*/
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, debug: false,
enableWorker: true, enableWorker: true,
lowLatencyMode: false, // Disabilitato LL per migliorare stabilità ABR su flussi standard lowLatencyMode: false, // Disabilitato per stabilità e buffer migliore
backBufferLength: 90 backBufferLength: 90,
}); maxBufferLength: 30, // Aumentato buffer (default 30s)
startLevel: -1, // Auto start
xhrSetup: function (xhr, url) {
xhr.withCredentials = false; // Fix CORS issues sometimes
}
};
/**
* Logica di Selezione Player
* Priorità: HLS.js (PC/Android) > Nativo (iOS) > Fallback (Direct Stream)
*/
function initStream() {
// STRATEGIA 1: HLS.js (Preferita per PC e Android)
if (Hls.isSupported()) {
console.log('Strategy: HLS.js (High Quality Control)');
hlsInstance = new Hls(hlsConfig);
hlsInstance.loadSource(streamHLS); hlsInstance.loadSource(streamHLS);
hlsInstance.attachMedia(audioPlayer); hlsInstance.attachMedia(audioPlayer);
hlsInstance.on(Hls.Events.MANIFEST_PARSED, function (event, data) { hlsInstance.on(Hls.Events.MANIFEST_PARSED, function (event, data) {
console.log('Manifest HLS caricato'); console.log(`HLS Manifest Loaded. Levels: ${data.levels.length}`);
console.log('Livelli trovati:', data.levels.length, data.levels);
// Se ci sono più livelli, prova a partire aggressivo se non specificato
if (data.levels.length > 1) { if (data.levels.length > 1) {
console.log('Multi-bitrate disponibile. Auto-switch abilitato.'); // Opzionale: Logica custom startLevel se necessario
// hlsInstance.startLevel = data.levels.length - 1;
// 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.');
} }
audioPlayer.play().then(() => { attemptPlay();
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) { hlsInstance.on(Hls.Events.ERROR, function (event, data) {
console.error('Errore HLS:', data.type, data.details);
if (data.fatal) { if (data.fatal) {
console.warn('HLS Fatal Error:', data.type);
switch (data.type) { switch (data.type) {
case Hls.ErrorTypes.NETWORK_ERROR: case Hls.ErrorTypes.NETWORK_ERROR:
console.log('Errore di rete, tentativo fallback...'); hlsInstance.startLoad();
tryFallbackStream();
break; break;
case Hls.ErrorTypes.MEDIA_ERROR: case Hls.ErrorTypes.MEDIA_ERROR:
console.log('Errore media, tentativo recupero...');
hlsInstance.recoverMediaError(); hlsInstance.recoverMediaError();
break; break;
default: default:
console.log('Errore fatale, tentativo fallback...'); fallbackToDirectStream();
tryFallbackStream();
break; break;
} }
} }
}); });
return true; return;
} else if (audioPlayer.canPlayType('application/vnd.apple.mpegurl')) {
console.log('HLS nativo supportato');
// Visual Debugging per Mobile
if (playerStatus) {
playerStatus.textContent = 'Caricamento HLS Nativo...';
} }
// STRATEGIA 2: Nativo (Obbligatorio per iOS)
if (audioPlayer.canPlayType('application/vnd.apple.mpegurl')) {
console.log('Strategy: Native HLS (iOS/Safari)');
audioPlayer.src = streamHLS; audioPlayer.src = streamHLS;
audioPlayer.play().then(() => { audioPlayer.preload = 'auto'; // Suggerisce al browser di caricare dati
console.log('Riproduzione HLS nativa avviata');
updatePlayState(true); // Hack per iOS: Alcune volte serve un tocco utente, gestito dal click play
if (playerStatus) playerStatus.textContent += ' (Native)'; // Debug visivo attemptPlay();
}).catch(err => { return;
console.error('Errore HLS nativo:', err);
tryFallbackStream();
});
return true;
} }
return false; // STRATEGIA 3: Fallback (Stream MP3 diretto)
console.log('Strategy: Direct Fallback');
fallbackToDirectStream();
} }
/** function fallbackToDirectStream() {
* Usa stream diretto come fallback
*/
function tryFallbackStream() {
console.log('Tentativo stream fallback:', streamFallback);
if (hlsInstance) { if (hlsInstance) {
hlsInstance.destroy(); hlsInstance.destroy();
hlsInstance = null; hlsInstance = null;
} }
streamType = 'direct';
audioPlayer.src = streamFallback; audioPlayer.src = streamFallback;
audioPlayer.load(); attemptPlay();
}
audioPlayer.play().then(() => { function attemptPlay() {
console.log('Riproduzione stream diretto avviata'); // Nota: chiamare .play() senza interazione utente fallirà.
updatePlayState(true); // Questa funzione prepara lo stato. Il vero play avviene al click.
if (playerStatus) playerStatus.textContent += ' (Fallback)'; // Debug visivo if (playerStatus) playerStatus.textContent = 'Pronto alla riproduzione';
}).catch(err => { }
console.error('Errore stream diretto:', err);
updatePlayState(false); function togglePlay() {
if (playerStatus) { if (audioPlayer.paused) {
playerStatus.textContent = 'Errore di riproduzione'; 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));
} }
}); });
} }
/**
* 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 { } 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(); audioPlayer.pause();
updatePlayState(false); updateUI(false);
console.log('Riproduzione in pausa'); }
} else { }
// Play
function updateUI(playing) {
isPlaying = playing;
playIcon.style.display = playing ? 'none' : 'block';
pauseIcon.style.display = playing ? 'block' : 'none';
if (playerStatus) {
playerStatus.textContent = playing ? 'In riproduzione' : 'In pausa';
}
}
// Listeners
playPauseBtn.onclick = (e) => {
e.preventDefault();
// Se non inizializzato (primo click), avvia stream
if (!audioPlayer.src && !hlsInstance) { if (!audioPlayer.src && !hlsInstance) {
// Prima riproduzione - inizializza lo stream initStream();
if (streamHLS && !initHLSStream()) { // Piccolo timeout per dare tempo al setup prima del play effettivo
// Se HLS fallisce, usa direttamente il fallback setTimeout(togglePlay, 50);
tryFallbackStream();
}
} else { } else {
// Riprendi riproduzione togglePlay();
audioPlayer.play().then(() => {
updatePlayState(true);
console.log('Riproduzione ripresa');
}).catch(err => {
console.error('Errore ripresa riproduzione:', err);
updatePlayState(false);
});
} }
} };
});
/** audioPlayer.onplay = () => updateUI(true);
* Event listeners audio player audioPlayer.onpause = () => updateUI(false);
*/
audioPlayer.addEventListener('play', function () {
updatePlayState(true);
});
audioPlayer.addEventListener('pause', function () { audioPlayer.onerror = (e) => {
updatePlayState(false); console.error('Audio Error:', audioPlayer.error);
}); if (playerStatus) playerStatus.textContent = 'Errore riproduzione';
updateUI(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.addEventListener('waiting', function () {
if (playerStatus) {
playerStatus.textContent = 'Buffering...';
}
});
audioPlayer.addEventListener('playing', function () {
if (playerStatus) {
playerStatus.textContent = 'In riproduzione...';
}
});
// Setup Media Session // 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.');
} }
/** /**