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>

301
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à
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() { function initStream() {
if (!window.Hls) { // STRATEGIA 1: HLS.js (Preferita per PC e Android)
console.error('HLS.js non caricato');
return false;
}
if (Hls.isSupported()) { if (Hls.isSupported()) {
console.log('HLS supportato - uso HLS.js'); console.log('Strategy: HLS.js (High Quality Control)');
hlsInstance = new Hls({
debug: false,
enableWorker: true,
lowLatencyMode: false, // Disabilitato LL per migliorare stabilità ABR su flussi standard
backBufferLength: 90
});
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...';
}
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 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();
} }
/** 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(() => {
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';
}
});
} }
/** function attemptPlay() {
* Aggiorna stato visuale play/pause // Nota: chiamare .play() senza interazione utente fallirà.
*/ // Questa funzione prepara lo stato. Il vero play avviene al click.
function updatePlayState(playing) { if (playerStatus) playerStatus.textContent = 'Pronto alla riproduzione';
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 togglePlay() {
* Gestione click play/pause if (audioPlayer.paused) {
*/ const playPromise = audioPlayer.play();
playPauseBtn.addEventListener('click', function () { if (playPromise !== undefined) {
if (!audioPlayer) return; playPromise.then(() => {
updateUI(true);
if (isPlaying) { }).catch(error => {
// Pausa console.error('Play prevented:', error);
audioPlayer.pause(); // Se HLS fallisce al play (es. stale manifest), prova fallback
updatePlayState(false); if (!audioPlayer.src.includes(streamFallback) && !hlsInstance) {
console.log('Riproduzione in pausa'); fallbackToDirectStream();
} else { audioPlayer.play().catch(e => console.error('Fallback failed:', e));
// Play }
if (!audioPlayer.src && !hlsInstance) {
// Prima riproduzione - inizializza lo stream
if (streamHLS && !initHLSStream()) {
// Se HLS fallisce, usa direttamente il fallback
tryFallbackStream();
}
} else {
// Riprendi riproduzione
audioPlayer.play().then(() => {
updatePlayState(true);
console.log('Riproduzione ripresa');
}).catch(err => {
console.error('Errore ripresa riproduzione:', err);
updatePlayState(false);
}); });
} }
}
});
/**
* Event listeners audio player
*/
audioPlayer.addEventListener('play', function () {
updatePlayState(true);
});
audioPlayer.addEventListener('pause', function () {
updatePlayState(false);
});
audioPlayer.addEventListener('error', function (e) {
console.error('Errore audio player (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 { } else {
if (playerStatus) { audioPlayer.pause();
playerStatus.textContent = 'Errore sconosciuto'; updateUI(false);
}
}
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 () { function updateUI(playing) {
isPlaying = playing;
playIcon.style.display = playing ? 'none' : 'block';
pauseIcon.style.display = playing ? 'block' : 'none';
if (playerStatus) { if (playerStatus) {
playerStatus.textContent = 'Buffering...'; playerStatus.textContent = playing ? 'In riproduzione' : 'In pausa';
} }
}); }
audioPlayer.addEventListener('playing', function () { // Listeners
if (playerStatus) { playPauseBtn.onclick = (e) => {
playerStatus.textContent = 'In riproduzione...'; 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 // 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.');
} }
/** /**