aggiunto player hls v.2
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
<changelog>
|
||||
|
||||
<version>
|
||||
<number>2.1.2 (c)</number>
|
||||
<number>2.1.2 (d)</number>
|
||||
<logs>
|
||||
<log>Correzione e bugfix di problematiche varie.</log>
|
||||
</logs>
|
||||
|
||||
301
js/app.js
301
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.');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user