Corriger les informations de position pour les flux continus
Cette modification corrige le calcul de la position et de la durée pour les flux continus (webradio) en ajustant la méthode `poll_and_emit_changes()` dans `pmocontrol`. Elle détecte maintenant les flux via `is_playing_a_stream()`, extrait la durée depuis les métadonnées DIDL, et calcule la position relative basée sur `track_start_time`. Les valeurs par défaut (zéro pour la position, none pour la durée) sont utilisées si aucune durée n'est disponible. Les fichiers modifiés incluent `musicrenderer.rs`, `CurrentTrack.vue` et `useRenderers.ts` pour assurer une gestion cohérente des données de position et de durée.
This commit is contained in:
15
Blackboard/Report/patch_position_info_for_stream.md
Normal file
15
Blackboard/Report/patch_position_info_for_stream.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# Rapport : Patch des informations de position pour les flux continus
|
||||
|
||||
## Résumé
|
||||
Modification de la méthode `poll_and_emit_changes()` dans la crate `pmocontrol` pour corriger les données de position et durée lorsqu'un renderer diffuse un flux continu (webradio). La méthode détecte maintenant si un flux est en cours via `is_playing_a_stream()` et applique un traitement spécifique : extraction de la durée depuis les métadonnées DIDL, et calcul de la position relative depuis `track_start_time` (qui est déjà maintenu à jour lors des changements de métadonnées). Si aucune durée n'est disponible, la position et la durée sont mises à zéro/none.
|
||||
|
||||
## Fichiers modifiés
|
||||
|
||||
1. `pmocontrol/src/music_renderer/musicrenderer.rs`
|
||||
- Modification de la méthode `poll_and_emit_changes()` pour patcher les informations de position lors de la détection d'un flux continu
|
||||
- Ajout du logging au niveau info lors de la détection d'un flux continu
|
||||
- Extraction conditionnelle de la durée depuis les métadonnées DIDL pour les streams
|
||||
- Calcul de la position relative basé sur `track_start_time` (différence entre now et track_start_time)
|
||||
- Retour de valeurs par défaut (zéro pour position, none pour duration) si aucune durée n'est disponible dans les métadonnées
|
||||
- Préservation de la logique existante pour les médias réguliers (non-streams)
|
||||
- Note : `track_start_time` est déjà maintenu à jour par la logique existante lors des changements de métadonnées
|
||||
20
Blackboard/Todo/patch_position_info_for_stream.md
Normal file
20
Blackboard/Todo/patch_position_info_for_stream.md
Normal file
@@ -0,0 +1,20 @@
|
||||
** Ce travail devra être réalisé en suivant scrupuleusement les consignes listées dans le fichier [@Rules_optimal.md](file:///Users/coissac/Sync/maison/Petite_maisons/src/pmomusic/Blackboard/Rules_optimal.md) **
|
||||
|
||||
Cette modification cible la cratte pmocontrol uniquement
|
||||
|
||||
Les musiques Renderer, Disposent maintenant d'une méthode leur permettant de savoir s'ils sont en train de diffuser une webradio via leur méthode is_playing_a_stream.
|
||||
|
||||
Il faut donc que dans la méthode poll_and_emit_changes On fait ce qui est nécessaire pour envoyer des données de position et de durée de track corrigée si l'on a is_playing_a_stream à vrai.
|
||||
|
||||
Si is_playing_a_stream à vrai:
|
||||
- Maintenir à jour la valeur `track_start_time` de la classe
|
||||
MusicRenderer En la mettant égale à now de metadata.
|
||||
- Loguer cet événement au niveau info.
|
||||
- Extraire la durée du morceau depuis les métadonnées
|
||||
fournies par la structure de position.
|
||||
- Si la durée est disponible:
|
||||
- Utilisez cette donnée pour la pousser sur le bus des événements.
|
||||
- calculer la position dans le flux comme la différence
|
||||
entre now et track_start_time.
|
||||
- Sinon: Envoyer zéro pour la position et none pour la duration.
|
||||
- Sinon, transmettre les données fournies comme actuellement.
|
||||
@@ -83,7 +83,9 @@ const progressPercent = computed(() => {
|
||||
const position = state.value?.position_ms;
|
||||
const duration = state.value?.duration_ms;
|
||||
if (position && duration && duration > 0) {
|
||||
return (position / duration) * 100;
|
||||
// Plafonner à 100% pour éviter que le curseur dépasse en cas de délai de mise à jour
|
||||
const percent = (position / duration) * 100;
|
||||
return Math.min(percent, 100);
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
@@ -123,6 +123,9 @@ function ensureSSEConnected() {
|
||||
break;
|
||||
|
||||
case "position_changed":
|
||||
// Mettre à jour position et durée de manière atomique pour garantir la cohérence
|
||||
// Le backend envoie TOUJOURS les deux valeurs (même si null)
|
||||
|
||||
// Convertir rel_time (HH:MM:SS) en millisecondes
|
||||
if (event.rel_time) {
|
||||
const parts = event.rel_time.split(":").map(Number);
|
||||
@@ -133,7 +136,11 @@ function ensureSSEConnected() {
|
||||
(parts[2] ?? 0)) *
|
||||
1000;
|
||||
}
|
||||
} else {
|
||||
// Si rel_time est null/undefined, mettre position à 0
|
||||
snapshot.state.position_ms = 0;
|
||||
}
|
||||
|
||||
// Convertir track_duration (HH:MM:SS) en millisecondes
|
||||
if (event.track_duration) {
|
||||
const parts = event.track_duration.split(":").map(Number);
|
||||
@@ -144,7 +151,15 @@ function ensureSSEConnected() {
|
||||
(parts[2] ?? 0)) *
|
||||
1000;
|
||||
}
|
||||
} else {
|
||||
// Si track_duration est null/undefined (flux continu sans durée),
|
||||
// mettre duration_ms à null pour afficher "--:--"
|
||||
snapshot.state.duration_ms = null;
|
||||
}
|
||||
|
||||
// Important: Trigger reactivity en réassignant l'objet complet
|
||||
// Cela garantit que position_ms et duration_ms sont mis à jour atomiquement
|
||||
snapshotState.snapshots.set(rendererId, { ...snapshot });
|
||||
break;
|
||||
|
||||
case "volume_changed":
|
||||
|
||||
@@ -99,6 +99,10 @@ struct MusicRendererState {
|
||||
/// Timestamp when the current track started playing.
|
||||
/// Used to calculate elapsed time when renderer returns unreliable position info.
|
||||
track_start_time: Option<SystemTime>,
|
||||
/// Current track duration (HH:MM:SS format).
|
||||
/// For continuous streams, this is kept stable and only updated when it increases
|
||||
/// (to avoid decreasing duration updates from radio metadata).
|
||||
current_track_duration: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for MusicRendererState {
|
||||
@@ -110,6 +114,7 @@ impl Default for MusicRendererState {
|
||||
sleep_timer: SleepTimer::default(),
|
||||
has_played_since_track_start: false,
|
||||
track_start_time: None,
|
||||
current_track_duration: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -405,7 +410,56 @@ impl MusicRenderer {
|
||||
}
|
||||
|
||||
// Now get the fully patched position (with correct track_start_time and rel_time)
|
||||
if let Ok(position) = self.playback_position() {
|
||||
if let Ok(mut position) = self.playback_position() {
|
||||
// For continuous streams, manage duration to prevent it from decreasing
|
||||
let is_stream = self.is_playing_a_stream();
|
||||
if is_stream {
|
||||
if let Some(ref new_duration) = position.track_duration {
|
||||
let mut state = self.state.lock().unwrap();
|
||||
|
||||
// Parse durations to compare (HH:MM:SS format)
|
||||
let parse_duration = |dur_str: &str| -> Option<u32> {
|
||||
let parts: Vec<&str> = dur_str.split(':').collect();
|
||||
if parts.len() == 3 {
|
||||
let h: u32 = parts[0].parse().ok()?;
|
||||
let m: u32 = parts[1].parse().ok()?;
|
||||
let s: u32 = parts[2].parse().ok()?;
|
||||
Some(h * 3600 + m * 60 + s)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
match &state.current_track_duration {
|
||||
Some(stored_duration) => {
|
||||
// Compare new duration with stored one
|
||||
if let (Some(stored_secs), Some(new_secs)) = (
|
||||
parse_duration(stored_duration),
|
||||
parse_duration(new_duration),
|
||||
) {
|
||||
if new_secs > stored_secs {
|
||||
// Duration increased: update stored value and use new one
|
||||
tracing::debug!(
|
||||
"MusicRenderer [{}]: Stream duration increased: {} -> {}",
|
||||
self.info.friendly_name(),
|
||||
stored_duration,
|
||||
new_duration
|
||||
);
|
||||
state.current_track_duration = Some(new_duration.clone());
|
||||
} else {
|
||||
// Duration decreased or equal: keep stored value
|
||||
position.track_duration = Some(stored_duration.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
// First time: store the duration
|
||||
state.current_track_duration = Some(new_duration.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let changed = watched
|
||||
.position
|
||||
.as_ref()
|
||||
@@ -1209,13 +1263,15 @@ impl MusicRenderer {
|
||||
}
|
||||
|
||||
/// Sets the last known track metadata.
|
||||
/// Updates track_start_time only if the metadata actually changes.
|
||||
/// Updates track_start_time and resets current_track_duration only if the metadata actually changes.
|
||||
pub fn set_last_metadata(&self, metadata: Option<TrackMetadata>) {
|
||||
let mut state = self.state.lock().unwrap();
|
||||
let metadata_changed = state.last_metadata != metadata;
|
||||
state.last_metadata = metadata;
|
||||
if metadata_changed {
|
||||
state.track_start_time = Some(SystemTime::now());
|
||||
// Reset duration cache when track changes (for streams)
|
||||
state.current_track_duration = None;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -313,49 +313,54 @@ impl PlaybackPosition for OpenHomeRenderer {
|
||||
let mut track_uri = None;
|
||||
let mut track_metadata_xml = None;
|
||||
|
||||
// Get track ID from playlist
|
||||
if let Some(playlist_client) = &self.playlist {
|
||||
match playlist_client.id() {
|
||||
Ok(id) => track_id = Some(id),
|
||||
Err(err) => debug!(
|
||||
// renderer = self.info.id.0.as_str(),
|
||||
error = %err,
|
||||
"Failed to read OpenHome track id"
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(info_client) = &self.info_client {
|
||||
match info_client.track() {
|
||||
Ok(track) => {
|
||||
track_uri = Some(track.uri.clone());
|
||||
track_metadata_xml = track.metadata_xml;
|
||||
// Use queue API to get current item with cached metadata
|
||||
let mut queue_guard = self.queue.lock().unwrap();
|
||||
if let Ok(Some((current_item, _))) = queue_guard.peek_current() {
|
||||
// Use metadata from queue cache (updated via OpenHome events)
|
||||
track_uri = Some(current_item.uri.clone());
|
||||
|
||||
// Check if the URI has changed to detect track changes
|
||||
let mut cached_uri = self.current_track_uri.lock().unwrap();
|
||||
let uri_changed = cached_uri.as_ref() != Some(&track.uri);
|
||||
// Build DIDL metadata XML from cached TrackMetadata
|
||||
if let Some(ref metadata) = current_item.metadata {
|
||||
track_metadata_xml = Some(
|
||||
crate::music_renderer::musicrenderer::build_didl_lite_metadata(
|
||||
metadata,
|
||||
¤t_item.uri,
|
||||
¤t_item.protocol_info,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if uri_changed {
|
||||
tracing::debug!(
|
||||
"OpenHome track URI changed: {:?} -> {:?}",
|
||||
cached_uri,
|
||||
track.uri
|
||||
);
|
||||
// Check if the URI has changed to detect track changes
|
||||
let mut cached_uri = self.current_track_uri.lock().unwrap();
|
||||
let uri_changed = cached_uri.as_ref() != Some(¤t_item.uri);
|
||||
|
||||
// Détecte si la nouvelle URL est un flux continu
|
||||
let is_stream = crate::music_renderer::is_continuous_stream_url(&track.uri);
|
||||
*self.continuous_stream.lock().unwrap() = is_stream;
|
||||
tracing::debug!("OpenHome URI changed, continuous_stream={}", is_stream);
|
||||
if uri_changed {
|
||||
tracing::debug!(
|
||||
"OpenHome track URI changed: {:?} -> {:?}",
|
||||
cached_uri,
|
||||
current_item.uri
|
||||
);
|
||||
|
||||
*cached_uri = Some(track.uri);
|
||||
}
|
||||
}
|
||||
Err(err) => debug!(
|
||||
// renderer = self.info.id.0.as_str(),
|
||||
error = %err,
|
||||
"Failed to read OpenHome track metadata"
|
||||
),
|
||||
// Détecte si la nouvelle URL est un flux continu
|
||||
let is_stream = crate::music_renderer::is_continuous_stream_url(¤t_item.uri);
|
||||
*self.continuous_stream.lock().unwrap() = is_stream;
|
||||
tracing::debug!("OpenHome URI changed, continuous_stream={}", is_stream);
|
||||
|
||||
*cached_uri = Some(current_item.uri.clone());
|
||||
}
|
||||
}
|
||||
drop(queue_guard);
|
||||
|
||||
// Get duration from Time service - duration_secs=0 means stream (no duration)
|
||||
let track_duration = if time_info.duration_secs == 0 {
|
||||
|
||||
Reference in New Issue
Block a user