From 9e8491710c1a27ddd13b27bdc935260fd8e81d04 Mon Sep 17 00:00:00 2001 From: Eric Coissac Date: Thu, 27 Nov 2025 20:27:52 +0100 Subject: [PATCH] debug du lecteur web --- .DS_Store | Bin 14340 -> 14340 bytes .../src/components/GenericMusicPlayer.vue | 267 +++++++++++++++--- pmoaudio-ext/src/sinks/streaming_flac_sink.rs | 21 +- pmoaudio-ext/src/sources/playlist_source.rs | 25 +- pmomediaserver/src/paradise_streaming.rs | 25 +- pmoparadise/examples/serve_channels.rs | 30 +- 6 files changed, 314 insertions(+), 54 deletions(-) diff --git a/.DS_Store b/.DS_Store index 009731bcead0ec391b9a7a7d7218a2d4d534717f..15627691ae7eb9406113b7611e969f05c860a898 100644 GIT binary patch delta 704 zcmd5&&r1Sf5Phqb5>gxXo5GfAF)H0`%L)pt3`AkOdnW5d>YOO9aVx^VS~_ zA$u<99|)rVqDyq?T5mUk=-Q=k7-pDxZ@xEEtyC*lM&}j)Hy{IL?7_q?ib=EfG$b_L z(;*Z3%DLXoX0PLzz_N=N!ui?vzDgCNz2(ZT^jgB z^WxO8Fy)m{N}A6>?&S5X#iiJ<9k91;*$_#8#X@WWv*r3)Gx*hXy_ONxk+{@xl>9cN> zXHTS$he)xXBR)%1hD?JK3~rmnGOJ(bvL|t-&2M%$`1y{wR(q^Vb @@ -251,12 +254,15 @@ const audioError = ref(null) const audioPlayer = ref(null) const currentTime = ref(0) const duration = ref(0) +const lastTimeUpdateTs = ref(null) +const lastLoggedTimeupdate = ref(0) +const lastReadyState = ref(null) +const lastNetworkState = ref(null) -// Retry logic for fragile browser decoders -const MAX_AUDIO_RETRIES = 3 -const RETRY_DELAY_MS = 800 // Réduit pour des transitions plus rapides -const retryCount = ref(0) +// Retry timer cleanup (auto-reload désactivé pour les streams live) let retryTimer: ReturnType | null = null +let stallMonitor: ReturnType | null = null +let stateMonitor: ReturnType | null = null // Metadata refresh via SSE let metadataEventSource: EventSource | null = null @@ -264,12 +270,69 @@ let metadataEventSource: EventSource | null = null // Load sources on mount onMounted(async () => { await loadSources() + // Monitor pour détecter une éventuelle stagnation du currentTime + stallMonitor = setInterval(() => { + const p = audioPlayer.value + if (!p || !isPlaying.value) return + if (!lastTimeUpdateTs.value) return + const now = Date.now() + // Si aucun timeupdate depuis >5s et lecture en cours, logguer l'état du buffer + if (now - lastTimeUpdateTs.value > 5000) { + const buffered: string[] = [] + for (let i = 0; i < p.buffered.length; i++) { + buffered.push(`${p.buffered.start(i).toFixed(3)}-${p.buffered.end(i).toFixed(3)}`) + } + console.debug('[PlayerDebug] stall-detected', { + ts: now, + currentTime: p.currentTime.toFixed(3), + duration: isFinite(p.duration) ? p.duration.toFixed(3) : 'NaN', + readyState: p.readyState, + networkState: p.networkState, + paused: p.paused, + buffered, + }) + // Eviter le spam : reset le marqueur pour n'émettre qu'une fois par stagnation + lastTimeUpdateTs.value = null + } + }, 2000) + + // Monitor périodique des changements de readyState/networkState + stateMonitor = setInterval(() => { + const p = audioPlayer.value + if (!p) return + const rs = p.readyState + const ns = p.networkState + if (rs !== lastReadyState.value || ns !== lastNetworkState.value) { + const buffered: string[] = [] + for (let i = 0; i < p.buffered.length; i++) { + buffered.push(`${p.buffered.start(i).toFixed(3)}-${p.buffered.end(i).toFixed(3)}`) + } + console.debug('[PlayerDebug] state-change', { + ts: Date.now(), + currentTime: p.currentTime.toFixed(3), + readyState: rs, + networkState: ns, + paused: p.paused, + buffered, + }) + lastReadyState.value = rs + lastNetworkState.value = ns + } + }, 1000) }) // Cleanup on unmount onUnmounted(() => { stopMetadataRefresh() clearRetryTimer() + if (stallMonitor) { + clearInterval(stallMonitor) + stallMonitor = null + } + if (stateMonitor) { + clearInterval(stateMonitor) + stateMonitor = null + } }) /** @@ -516,6 +579,18 @@ function handleAudioEnded() { currentTime.value = 0 stopMetadataRefresh() // On peut implémenter ici une logique de lecture automatique du prochain morceau + const p = audioPlayer.value + if (p) { + console.debug('[PlayerDebug] ended', { + ts: Date.now(), + currentTime: p.currentTime.toFixed(3), + duration: isFinite(p.duration) ? p.duration.toFixed(3) : 'NaN', + readyState: p.readyState, + networkState: p.networkState, + }) + } else { + console.debug('[PlayerDebug] ended (no player)') + } } /** @@ -530,16 +605,46 @@ function handleAudioError() { ? `Erreur de lecture audio (code ${errorCode})` : 'Erreur de lecture audio inconnue' - if (selectedSource.value && currentTrack.value && retryCount.value < MAX_AUDIO_RETRIES) { - isPlaying.value = false - retryCount.value += 1 - audioError.value = `${baseMessage} - tentative de reprise (${retryCount.value}/${MAX_AUDIO_RETRIES})` - scheduleRetryPlayback() - return - } - + // Pour les streams live (Radio Paradise), ne pas recharger le flux : cela remet la progression à 0 + // et crée un lag perceptible. On se contente d'afficher l'erreur et on tente un play() direct si possible. audioError.value = baseMessage isPlaying.value = false + + // Tenter une reprise douce sans reload si le player a déjà des données bufferisées + const player = audioPlayer.value + if (player && !player.paused) { + // Rien à faire, il lit encore ou se relancera tout seul + return + } + if (player && player.readyState >= 2) { + console.debug('[PlayerDebug] error', { + ts: Date.now(), + message: baseMessage, + code: audio?.error?.code, + currentTime: player.currentTime.toFixed(3), + duration: isFinite(player.duration) ? player.duration.toFixed(3) : 'NaN', + readyState: player.readyState, + networkState: player.networkState, + buffered: (() => { + const arr: string[] = [] + for (let i = 0; i < player.buffered.length; i++) { + arr.push(`${player.buffered.start(i).toFixed(3)}-${player.buffered.end(i).toFixed(3)}`) + } + return arr + })(), + }) + void player.play().catch(() => { + // Si play échoue (user gesture requis ou decode error), on laisse l'erreur affichée + }) + } else { + console.debug('[PlayerDebug] error (no retry)', { + ts: Date.now(), + message: baseMessage, + code: audio?.error?.code, + readyState: player?.readyState, + networkState: player?.networkState, + }) + } } /** @@ -557,6 +662,21 @@ function handleAudioPlay() { function handleAudioPause() { isPlaying.value = false // Don't stop refresh on pause, user might resume + const p = audioPlayer.value + if (p) { + const buffered: string[] = [] + for (let i = 0; i < p.buffered.length; i++) { + buffered.push(`${p.buffered.start(i).toFixed(3)}-${p.buffered.end(i).toFixed(3)}`) + } + console.debug('[PlayerDebug] pause', { + ts: Date.now(), + currentTime: p.currentTime.toFixed(3), + readyState: p.readyState, + networkState: p.networkState, + paused: p.paused, + buffered, + }) + } } /** @@ -565,6 +685,45 @@ function handleAudioPause() { function handleTimeUpdate() { if (audioPlayer.value) { currentTime.value = audioPlayer.value.currentTime + lastTimeUpdateTs.value = Date.now() + + // Log périodiquement l'état du buffer pour diagnostiquer les coupures + const logEvery = 2 // secondes (plus serré pour capturer les transitions) + if (Math.floor(currentTime.value) % logEvery === 0 && Math.floor(currentTime.value) !== lastLoggedTimeupdate.value) { + const p = audioPlayer.value + const buffered: string[] = [] + for (let i = 0; i < p.buffered.length; i++) { + buffered.push(`${p.buffered.start(i).toFixed(3)}-${p.buffered.end(i).toFixed(3)}`) + } + console.debug('[PlayerDebug] timeupdate', { + ts: Date.now(), + currentTime: p.currentTime.toFixed(3), + duration: isFinite(p.duration) ? p.duration.toFixed(3) : 'NaN', + readyState: p.readyState, + networkState: p.networkState, + paused: p.paused, + buffered, + }) + lastLoggedTimeupdate.value = Math.floor(currentTime.value) + } + + // Si le temps recule (changement de piste) logguer explicitement l'état + if (currentTime.value < lastLoggedTimeupdate.value) { + const p = audioPlayer.value + const buffered: string[] = [] + for (let i = 0; i < p.buffered.length; i++) { + buffered.push(`${p.buffered.start(i).toFixed(3)}-${p.buffered.end(i).toFixed(3)}`) + } + console.debug('[PlayerDebug] time-jump', { + ts: Date.now(), + currentTime: p.currentTime.toFixed(3), + duration: isFinite(p.duration) ? p.duration.toFixed(3) : 'NaN', + readyState: p.readyState, + networkState: p.networkState, + paused: p.paused, + buffered, + }) + } } } @@ -593,6 +752,60 @@ function handleCanPlay() { console.log('Audio ready to play (canplay event)') } +function handleAudioWaiting() { + const p = audioPlayer.value + if (!p) return + const buffered: string[] = [] + for (let i = 0; i < p.buffered.length; i++) { + buffered.push(`${p.buffered.start(i).toFixed(3)}-${p.buffered.end(i).toFixed(3)}`) + } + console.debug('[PlayerDebug] waiting', { + ts: Date.now(), + currentTime: p.currentTime.toFixed(3), + duration: isFinite(p.duration) ? p.duration.toFixed(3) : 'NaN', + readyState: p.readyState, + networkState: p.networkState, + paused: p.paused, + buffered, + }) +} + +function handleAudioStalled() { + const p = audioPlayer.value + if (!p) return + const buffered: string[] = [] + for (let i = 0; i < p.buffered.length; i++) { + buffered.push(`${p.buffered.start(i).toFixed(3)}-${p.buffered.end(i).toFixed(3)}`) + } + console.debug('[PlayerDebug] stalled', { + ts: Date.now(), + currentTime: p.currentTime.toFixed(3), + duration: isFinite(p.duration) ? p.duration.toFixed(3) : 'NaN', + readyState: p.readyState, + networkState: p.networkState, + paused: p.paused, + buffered, + }) +} + +function handleAudioProgress() { + const p = audioPlayer.value + if (!p) return + const buffered: string[] = [] + for (let i = 0; i < p.buffered.length; i++) { + buffered.push(`${p.buffered.start(i).toFixed(3)}-${p.buffered.end(i).toFixed(3)}`) + } + console.debug('[PlayerDebug] progress', { + ts: Date.now(), + currentTime: p.currentTime.toFixed(3), + duration: isFinite(p.duration) ? p.duration.toFixed(3) : 'NaN', + readyState: p.readyState, + networkState: p.networkState, + paused: p.paused, + buffered, + }) +} + /** * Formatte le temps en MM:SS */ @@ -752,25 +965,11 @@ function clearRetryTimer() { } } -/** - * Réinitialise les compteurs de retry - */ +// Réinitialise l'état de retry (timer uniquement) function resetAudioRetries() { - retryCount.value = 0 clearRetryTimer() } -/** - * Lance un retry différé pour laisser le temps au backend de reproposer le flux - */ -function scheduleRetryPlayback() { - clearRetryTimer() - retryTimer = setTimeout(() => { - retryTimer = null - void retryPlayback() - }, RETRY_DELAY_MS) -} - /** * (Ré)initialise la source audio et démarre la lecture */ @@ -852,20 +1051,6 @@ async function waitForSufficientBuffer(player: HTMLAudioElement): Promise }) } -/** - * Tente de relancer la lecture après une erreur (nouvelle résolution d'URI) - */ -async function retryPlayback() { - if (!selectedSource.value || !currentTrack.value) return - - try { - const result = await resolveUri(selectedSource.value.id, currentTrack.value.id) - await startPlaybackFromUri(result.uri) - } catch (e: any) { - audioError.value = `Erreur lors de la reprise: ${e.message}` - isPlaying.value = false - } -}