Fix progressive cache: distinguish temporary EOF from real EOF

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).
This commit is contained in:
Claude
2025-11-07 13:10:35 +00:00
parent d8594e72ad
commit 58e6753a81
2 changed files with 29 additions and 6 deletions

View File

@@ -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;
}

View File

@@ -716,6 +716,27 @@ impl<C: CacheConfig> Cache<C> {
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.