From 58e6753a811ad22531db6f6864c79cd19b711031 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 7 Nov 2025 13:10:35 +0000 Subject: [PATCH] Fix progressive cache: distinguish temporary EOF from real EOF MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem: - PlaylistSource reads cached files faster than FlacCacheSink writes them - FLAC decoder encounters EOF and stops playback prematurely - First track doesn't play completely (stops at prebuffer point ~600ms) - Needed to differentiate: * Temporary EOF: file still being written (wait and retry) * Real EOF: file completely written (stop decoding) Solution: 1. Added Cache::is_download_complete() method (pmocache/src/cache.rs:735) - Checks for existence of completion marker (.complete file) - Marker created only when file is fully written and closed - Fast synchronous check (no async overhead) 2. Modified decode_and_emit_track() (playlist_source.rs:337) - On EOF: check if completion marker exists - If no marker: file still being written → wait 50ms and retry read - If marker exists: file complete → finish decoding - Reduced wait from 100ms to 50ms for better responsiveness Benefits: ✅ First track now plays completely (not just prebuffer portion) ✅ Progressive caching still works (playback starts at ~600ms) ✅ Proper EOF handling (no premature stops) ✅ Efficient polling (50ms retry interval) ✅ Works for both fresh downloads and cached files Tested: - Fresh download: EOF retries visible in logs every ~50ms - File plays until completion marker created - No premature track termination Related to previous optimization (commit d8594e7) that made prebuffer→playlist push immediate (76ms instead of 19s). --- pmoaudio-ext/src/sources/playlist_source.rs | 14 ++++++++------ pmocache/src/cache.rs | 21 +++++++++++++++++++++ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/pmoaudio-ext/src/sources/playlist_source.rs b/pmoaudio-ext/src/sources/playlist_source.rs index 93fd6c3d..f2896e11 100644 --- a/pmoaudio-ext/src/sources/playlist_source.rs +++ b/pmoaudio-ext/src/sources/playlist_source.rs @@ -332,15 +332,17 @@ async fn decode_and_emit_track( // Si EOF atteint (read == 0) if read == 0 { - // Vérifier si le download est toujours en cours (cache progressif) - if cache.get_download(cache_pk).await.is_some() { - // Download en cours - attendre un peu et réessayer - tracing::trace!("decode_and_emit_track: EOF reached but download ongoing, waiting..."); - tokio::time::sleep(Duration::from_millis(100)).await; + // Vérifier si le fichier est complètement écrit (completion marker existe) + // Si pas de marker, le fichier est encore en cours d'écriture (cache progressif) + if !cache.is_download_complete(cache_pk) { + // Fichier encore en cours d'écriture - attendre un peu et réessayer + tracing::trace!("decode_and_emit_track: EOF reached but file not complete (no marker), waiting 50ms..."); + tokio::time::sleep(Duration::from_millis(50)).await; continue; // Retry la lecture } - // Download terminé - c'est vraiment la fin du fichier + // Completion marker existe - c'est vraiment la fin du fichier + tracing::trace!("decode_and_emit_track: EOF reached and file is complete (marker exists)"); if pending.is_empty() { break; } diff --git a/pmocache/src/cache.rs b/pmocache/src/cache.rs index d9fe6ff5..4ba635da 100755 --- a/pmocache/src/cache.rs +++ b/pmocache/src/cache.rs @@ -716,6 +716,27 @@ impl Cache { downloads.get(pk).cloned() } + /// Vérifie si le téléchargement/ingestion d'un fichier est complètement terminé + /// + /// Cette méthode vérifie l'existence du fichier marker de complétion (.complete) + /// qui est créé uniquement quand le fichier est complètement écrit et fermé. + /// + /// Utile pour différencier: + /// - EOF temporaire : fichier encore en cours d'écriture (retourne false) + /// - EOF réel : fichier complètement écrit (retourne true) + /// + /// # Arguments + /// + /// * `pk` - Clé primaire du fichier + /// + /// # Returns + /// + /// `true` si le fichier est complètement écrit (marker existe), `false` sinon + pub fn is_download_complete(&self, pk: &str) -> bool { + let completion_marker = self.get_completion_marker_path(pk); + completion_marker.exists() + } + /// Retourne la taille actuelle téléchargée (source) /// /// Si le download est en cours, retourne la taille téléchargée.