From 2b0def806a3bc16467ab39ef274eb97c6c6abbde Mon Sep 17 00:00:00 2001 From: Eric Coissac Date: Sun, 1 Mar 2026 23:55:40 +0100 Subject: [PATCH] =?UTF-8?q?Ajout=20de=20la=20gestion=20de=20la=20position?= =?UTF-8?q?=20en=20temps=20r=C3=A9el=20et=20am=C3=A9lioration=20de=20la=20?= =?UTF-8?q?gestion=20de=20la=20dur=C3=A9e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ajout de l'événement Position pour envoyer la position courante ~1/s pendant la lecture. Modification de la gestion de la durée dans le registry pour ne pas écraser une durée déjà connue par la source (priorité à la source pour les flux radio). Mise à jour du handler position_update pour utiliser update_duration au lieu de update_position, et suppression de la gestion de position_sec qui est maintenant gérée par PlayerEvent::Position. Le serveur gère maintenant la position via les événements Position émis par PlayerSource, et la durée est uniquement utilisée comme fallback pour les sources sans durée connue. --- pmoaudio-ext/src/sources/player_source.rs | 13 +++++++++++++ pmowebrenderer/src/pipeline.rs | 3 +++ pmowebrenderer/src/register.rs | 4 +++- pmowebrenderer/src/registry.rs | 16 ++++++++++------ 4 files changed, 29 insertions(+), 7 deletions(-) diff --git a/pmoaudio-ext/src/sources/player_source.rs b/pmoaudio-ext/src/sources/player_source.rs index 7fdfecfa..4e81fe2b 100644 --- a/pmoaudio-ext/src/sources/player_source.rs +++ b/pmoaudio-ext/src/sources/player_source.rs @@ -79,6 +79,8 @@ pub enum PlayerEvent { Stopped, /// Fin de piste (pour que le ControlPoint avance la queue) TrackEnded, + /// Position courante (émise ~1/s pendant la lecture) + Position { position_sec: f64 }, /// Erreur lors de l'ouverture ou de la lecture Error(String), } @@ -345,6 +347,9 @@ impl PlayerSourceLogic { let source_stop = stop_token.child_token(); let source_stop_clone = source_stop.clone(); + // Dernière seconde entière pour laquelle on a émis un Position + let mut last_reported_sec: i64 = -1; + // Spawner l'émission de la source dans une tâche séparée let emit_task = tokio::spawn(async move { source.emit_to_channel(&chunk_tx, &source_stop_clone).await @@ -443,6 +448,14 @@ impl PlayerSourceLogic { // Mettre à jour la position courante if seg.is_audio_chunk() { *paused_at_sec = seg.timestamp_sec; + // Émettre Position ~1/s + let sec = paused_at_sec.floor() as i64; + if sec != last_reported_sec { + last_reported_sec = sec; + let _ = self.event_tx.send(PlayerEvent::Position { + position_sec: *paused_at_sec, + }); + } } // Envoyer au pipeline en aval if let Err(e) = send_to_children("PlayerSource", output, seg).await { diff --git a/pmowebrenderer/src/pipeline.rs b/pmowebrenderer/src/pipeline.rs index 90edd380..843f79c6 100644 --- a/pmowebrenderer/src/pipeline.rs +++ b/pmowebrenderer/src/pipeline.rs @@ -162,6 +162,9 @@ async fn run_event_listener( s.playback_state = PlaybackState::Stopped; s.position = None; } + PlayerEvent::Position { position_sec } => { + state.write().position = Some(seconds_to_upnp_time(position_sec)); + } PlayerEvent::TrackEnded => { #[cfg(feature = "pmoserver")] { diff --git a/pmowebrenderer/src/register.rs b/pmowebrenderer/src/register.rs index ce9bba75..96a156f5 100644 --- a/pmowebrenderer/src/register.rs +++ b/pmowebrenderer/src/register.rs @@ -68,12 +68,14 @@ pub struct PositionUpdateRequest { } /// POST /api/webrenderer/{id}/position +/// position_sec est ignoré (géré par PlayerEvent::Position côté serveur). +/// duration_sec est utilisé comme fallback si la source ne connaît pas la durée (flux radio). pub async fn position_update_handler( State(registry): State>, Path(instance_id): Path, Json(req): Json, ) -> impl IntoResponse { - registry.update_position(&instance_id, req.position_sec, req.duration_sec); + registry.update_duration(&instance_id, req.duration_sec); StatusCode::NO_CONTENT } diff --git a/pmowebrenderer/src/registry.rs b/pmowebrenderer/src/registry.rs index cbc4b6be..25650dc5 100644 --- a/pmowebrenderer/src/registry.rs +++ b/pmowebrenderer/src/registry.rs @@ -152,15 +152,19 @@ impl RendererRegistry { .map(|i| i.device_instance.clone()) } - /// Met à jour la position et la durée depuis le navigateur (audio.currentTime). - pub fn update_position(&self, instance_id: &str, position_sec: f64, duration_sec: Option) { + /// Met à jour la durée depuis le navigateur. + /// La position est gérée par PlayerSource via PlayerEvent::Position. + /// On n'utilise duration_sec que si la source ne la connaît pas (flux radio sans durée). + pub fn update_duration(&self, instance_id: &str, duration_sec: Option) { let instances = self.instances.read(); if let Some(instance) = instances.get(instance_id) { let mut s = instance.state.write(); - s.position = Some(crate::pipeline::seconds_to_upnp_time(position_sec)); - if let Some(dur) = duration_sec { - if dur > 0.0 { - s.duration = Some(crate::pipeline::seconds_to_upnp_time(dur)); + // N'écraser la durée que si elle n'est pas déjà connue (la source est prioritaire) + if s.duration.is_none() { + if let Some(dur) = duration_sec { + if dur > 0.0 { + s.duration = Some(crate::pipeline::seconds_to_upnp_time(dur)); + } } } }