Second round

This commit is contained in:
2025-11-17 02:56:19 +01:00
parent 765070c4b0
commit cbe197da9c
2 changed files with 53 additions and 64 deletions

View File

@@ -475,11 +475,6 @@ impl NodeLogic for StreamingOggFlacSinkLogic {
break; break;
} }
SyncMarker::TopZeroSync => {
self.ogg_broadcast.mark_top_zero();
trace!("TopZeroSync propagated to timed broadcast");
}
_ => { _ => {
trace!("Received other sync marker"); trace!("Received other sync marker");
} }

View File

@@ -83,7 +83,7 @@ struct State<T> {
last_segment_end: Option<Instant>, last_segment_end: Option<Instant>,
cursors: Vec<Weak<ReceiverCursor>>, cursors: Vec<Weak<ReceiverCursor>>,
initialized: bool, initialized: bool,
saw_positive_timestamp: bool, last_purge: Instant,
} }
impl<T> State<T> { impl<T> State<T> {
@@ -98,16 +98,24 @@ impl<T> State<T> {
last_segment_end: None, last_segment_end: None,
cursors: Vec::new(), cursors: Vec::new(),
initialized: false, initialized: false,
saw_positive_timestamp: false, last_purge: epoch_start,
} }
} }
fn purge_expired(&mut self) -> bool { fn purge_expired(&mut self) -> bool {
let now = Instant::now();
// Throttling : purger au maximum toutes les 100ms
if now.duration_since(self.last_purge) < Duration::from_millis(100) {
return false;
}
self.last_purge = now;
let mut purged = 0u64; let mut purged = 0u64;
while let Some(entry) = self.buffer.front() { while let Some(entry) = self.buffer.front() {
if entry.expires_at <= Instant::now() { if entry.expires_at <= now {
let delta=Instant::now()-entry.expires_at; let delta = now - entry.expires_at;
info!("TimedBroadcast: purging expired packet (epoch={},delta={})",entry.epoch,delta.as_millis()); // info!("TimedBroadcast: purging expired packet (epoch={},delta={})", entry.epoch, delta.as_millis());
self.buffer.pop_front(); self.buffer.pop_front();
self.head_seq += 1; self.head_seq += 1;
purged += 1; purged += 1;
@@ -262,61 +270,56 @@ impl<T> Sender<T> {
// Détecter si c'est un TopZero // Détecter si c'est un TopZero
let is_top_zero = audio_timestamp == 0.0; let is_top_zero = audio_timestamp == 0.0;
// Gérer l'initialisation ET le TopZero ensemble // Gérer l'initialisation
if !state.initialized { if !state.initialized {
let now = Instant::now(); if !is_top_zero {
if is_top_zero { warn!(
// Premier paquet = TopZero → epoch commence à 0 "TimedBroadcast: First packet should have timestamp=0.0, got {:.3}s",
state.epoch_start = now;
state.epoch = 0;
info!("TimedBroadcast: initialized with TopZero (epoch=0)");
} else {
// Premier paquet avec ts > 0 → calculer epoch_start rétroactif
let offset = Duration::from_secs_f64(audio_timestamp);
state.epoch_start = now.checked_sub(offset).unwrap_or(now);
state.epoch = 0;
info!(
"TimedBroadcast: initialized with ts={:.3}s (epoch=0)",
audio_timestamp audio_timestamp
); );
} }
let now = Instant::now();
state.epoch_start = now;
state.epoch = 0;
state.initialized = true; state.initialized = true;
state.saw_positive_timestamp = !is_top_zero; info!("TimedBroadcast: initialized (epoch=0)");
} else if is_top_zero { } else if is_top_zero {
// TopZero sur un channel déjà initialisé // TopZero = nouveau segment, toujours valide après l'initialisation
let allow_reset = state.saw_positive_timestamp; let now = Instant::now();
if allow_reset { // Continuité temporelle : nouveau segment commence après le précédent
let now = Instant::now(); state.epoch_start = state.last_segment_end.unwrap_or(now);
state.epoch_start = state state.epoch = state.epoch.wrapping_add(1);
.last_segment_end info!(
.map(|end| end.max(now)) "TimedBroadcast: new epoch={} (continuous={})",
.unwrap_or(now); state.epoch,
state.epoch = state.epoch.wrapping_add(1); state.last_segment_end.is_some()
state.saw_positive_timestamp = false; // Reset pour le prochain cycle );
info!(
"TimedBroadcast: TopZero detected, new epoch={} (had_last_segment={})",
state.epoch,
state.last_segment_end.is_some()
);
} else {
warn!(
"TimedBroadcast: Ignoring duplicate TopZero (epoch={})",
state.epoch
);
}
} else if audio_timestamp > 0.0 {
state.saw_positive_timestamp = true;
} }
// 1. Calculer l'expiration du paquet actuel
let expires_at = state.epoch_start + Duration::from_secs_f64(audio_timestamp + segment_duration);
// 2. Vérifier que le paquet n'est pas déjà expiré
let now = Instant::now();
if expires_at <= now {
warn!(
"TimedBroadcast: packet already expired (ts={:.3}s, delta={}ms)",
audio_timestamp,
now.duration_since(expires_at).as_millis()
);
// On peut décider de l'ignorer ou de continuer
// Pour l'instant on continue pour ne pas bloquer le flux
}
// 3. Purger les paquets expirés et consommés
let consumed = state.prune_consumed(); let consumed = state.prune_consumed();
let expired = state.purge_expired(); let expired = state.purge_expired();
if consumed || expired { if consumed || expired {
self.inner.space_notify.notify_waiters(); self.inner.space_notify.notify_waiters();
} }
// 4. Vérifier la capacité et insérer
if state.buffer.len() < self.inner.capacity { if state.buffer.len() < self.inner.capacity {
// Le paquet expire à la fin de son segment audio
let expires_at = state.epoch_start + Duration::from_secs_f64(audio_timestamp + segment_duration);
let entry = Entry { let entry = Entry {
seq: state.next_seq, seq: state.next_seq,
expires_at, expires_at,
@@ -327,8 +330,10 @@ impl<T> Sender<T> {
state.next_seq += 1; state.next_seq += 1;
state.buffer.push_back(entry); state.buffer.push_back(entry);
// Stocker la fin de ce segment pour la continuité temporelle // 5. Stocker la fin du segment SEULEMENT pour les paquets non-TopZero
state.last_segment_end = Some(expires_at); if !is_top_zero {
state.last_segment_end = Some(expires_at);
}
let receivers = self.inner.receiver_count.load(Ordering::SeqCst); let receivers = self.inner.receiver_count.load(Ordering::SeqCst);
drop(state); drop(state);
@@ -375,17 +380,6 @@ impl<T> Sender<T> {
} }
} }
/// Marque un TopZero : DEPRECATED - no-op pour compatibilité.
///
/// Le vrai TopZero est maintenant détecté automatiquement dans send()
/// quand audio_timestamp == 0.0. Cette méthode est conservée pour
/// compatibilité avec le code existant mais ne fait rien.
pub fn mark_top_zero(&self) {
trace!("TimedBroadcast: mark_top_zero() called but ignored (auto-detection active)");
// No-op - TopZero est maintenant détecté automatiquement dans send()
// quand audio_timestamp == 0.0
}
/// Nombre actuel de receivers abonnés. /// Nombre actuel de receivers abonnés.
pub fn receiver_count(&self) -> usize { pub fn receiver_count(&self) -> usize {
self.inner.receiver_count.load(Ordering::SeqCst) self.inner.receiver_count.load(Ordering::SeqCst)