diff --git a/.kilo/plans/1775302116634-sunny-nebula.md b/.kilo/plans/1775302116634-sunny-nebula.md new file mode 100644 index 00000000..11d01b94 --- /dev/null +++ b/.kilo/plans/1775302116634-sunny-nebula.md @@ -0,0 +1,151 @@ +# Plan: pmowebrenderer - Améliorations et Multi-client avec DSP + +## Objectifs + +1. **Améliorer l'intégration UPnP Control** - Meilleur fonctionnement des commandes Play/Pause/Seek + - Contrôle piloté près de la sortie (streaming) plutôt qu'au début du pipeline + - Pour Pause: latence actuelle trop importante + - Play/Pause/Seek doivent fonctionner simultanément sur tous les clients + +2. **Améliorer la performance - Latence** - Réduire le délai entre l'envoi et la lecture + +3. **Ajouter le support multi-client avec DSP** - Chaque client peut avoir son propre pipeline DSP + +## Comportement UPnP Control + +### Mode Radio (flux infini) +- Pas de pause possible, hanya next ou stop +- Seek n'a pas de sens + + +### Architecture +``` +PlayerSource → ResamplingNode → ToI24Node + ├──→ [DSP Client 1] → StreamingOggFlacSink 1 + ├──→ [DSP Client 2] → StreamingOggFlacSink 2 + └──→ ... (dynamique) +``` + +Le control point UPnP voit UN seul Media Renderer. Les commandes Play/Pause/Seek affectent TOUTES les sorties client simultanément. + +## État Actuel + +Le pipeline actuel est linéaire pour un seul client: +``` +PlayerSource → ResamplingNode (96kHz) → ToI24Node → StreamingOggFlacSink +``` + +**Note importante:** Utiliser les crates pmoaudio et pmoaudio-ext existantes. Il est possible d'avoir plusieurs `StreamingOggFlacSink` consommant le même flux. Après ToI24Node, brancher en étoiles les différents DSP pour les différents clients. + +## Plan d'Implémentation + +### Phase 1: Amélioration UPnP Control + +1. **Analyser les handlers existants** dans `handlers.rs` +2. **Identifier les problèmes** avec Play/Pause/Seek: + - Timing des transitions d'état + - Gestion des erreurs + - Synchronisation entre clients HTTP et état UPnP +3. **Améliorer la fiabilité** des commandes + - Piloter le contrôle près de la sortie (streaming) + - Différerencier le comportement radio vs piste finie + +### Phase 2: Amélioration Latence + +1. **Réduire le buffer** dans `StreamingOggFlacSink` +2. **Optimiser le pacing** (actuellement max 0.5s ahead) +3. **Améliorer la directité** du chemin audio + +### Phase 3: Architecture Multi-client avec DSP + +1. **Refactorer le pipeline** pour supporter plusieurs clients comme décrit ci-dessus + +2. **Créer un système de DSP** dans pmoaudio ou pmoaudio-ext: + - Interface commune pour les effets audio + - Config des DSP via PMOconfig + - Room correction: equalizer, FIR filter, delay, gain + +3. **Gérer le cycle de vie**: + - Création du pipeline par client + - Nettoyage lors de la déconnexion + - Partage de la source commune entre clients + +## Fichiers à Modifier + +- `pipeline.rs` - Refactoring pour multi-client +- `handlers.rs` - Amélioration UPnP control +- `stream.rs` - Gestion multi-client +- `state.rs` - État par client +- pmoaudio ou pmoaudio-ext pour les mécanismes DSP + +## Défis Potentiels + +- Performance CPU avec plusieurs clients +- Synchronisation des clients avec le même contenu +- Gestion du gapless entre les pistes avec multi-client + +### Gestion de la Pause + +**Option recommandée: Silence (zéros)** +- Pendant la pause, continuer à envoyer des zéros encodés en FLAC +- Le client HTTP maintient sa connexion TCP alive +- Pas de reconnexion nécessaire quand on reprend la lecture +- Avantage: Seamless pour le client + +**Pourquoi pas réduction du sample rate:** +- Le header FLAC définit le sample rate en固定entête +- Changer le sample rate en cours de flux invalidate le flux entier +- Rebuild du flux serait plus complexe que le gain obtenu +- FLAC compresse très bien les zéros de toute façon (beaucoup de répétitions) + +**Autre option envisagée mais non recommandée:** +- Suspendre l'envoi: Le client HTTP va timeout et se déconnecter +- Segment OGG avec metadata: Complexe à implémenter, nécessite modification du client + +### Phase 0: StreamingOggFlacSink avec contrôle Pause + +**Distinction Radio vs Pistes finies:** + +| Mode | Comportement pendant Pause | +|------|---------------------------| +| **Radio (flux infini)** | Les chunks qui arrivent sont ignorés/perdus. On envoie du silence. La source continue à produire mais on n'en tient pas compte. | +| **Pistes finies** | On bloque la consommation des chunks. Par backpressure, le pipeline en amont s'arrête (TimerBufferNode arrête d'envoyer). La lecture est truly arrêtée. | + +**Architecture actuelle analysée:** +``` +AudioSegment → StreamingOggFlacSink → FLAC encoder → OGG wrapper → timed_broadcast → clients +``` + +**Implémentation suggérée:** + +1. **État de lecture distingué:** + - `PlaybackMode::Radio` - ignore les chunks entrants pendant pause + - `PlaybackMode::Track` - bloque la consommation (backpressure) + +2. **Dans SharedSinkContext:** + ```rust + pub enum PlaybackMode { + Radio, // Flux infini - ignore chunks pendant pause + Track, // Piste finie - block par backpressure + } + + pub playback_mode: PlaybackMode, + pub is_paused: Arc, + ``` + +3. **Traitement différent selon le mode:** + - **Radio**: Si `is_paused`, envoyer silence (zéros) mais perdre les chunks entrants + - **Track**: Si `is_paused`, ne pas consommer les chunks → backpressure → arrêt du pipeline en amont + +4. **Transition automatique:** + - Détecter le type de contenu via les métadonnées du TrackBoundary + - **Enrichir TrackBoundary** avec un champ `stream_type`: + ```rust + pub enum StreamType { + Continuous, // Radio/webcast - flux infini + Finite, // Piste/album - flux avec fin définie + } + + pub stream_type: StreamType, + ``` + - Si durée inconnue = Radio (Continuous), si durée connue = Track (Finite) diff --git a/.kilo/plans/1775382570642-kind-falcon.md b/.kilo/plans/1775382570642-kind-falcon.md new file mode 100644 index 00000000..9dc022df --- /dev/null +++ b/.kilo/plans/1775382570642-kind-falcon.md @@ -0,0 +1,58 @@ +# Refonte de `pmowebrenderer` – Élimination des redondances + +## Objectif +Réduire la duplication de code entre les fonctions de construction de services UPnP (`build_avtransport`, `build_renderingcontrol`, `build_connectionmanager`) et les macros d’ajout d’arguments (`add_arg_in!`, `add_arg_out!`). +Cela améliore la maintenabilité, la lisibilité et diminue le risque d’incohérences. + +## Étapes détaillées + +1. **Création d’une fonction générique `build_service`** + - Signature proposée: + ```rust + fn build_service( + name: &str, + variables: Vec>, + actions: Vec, + handlers: Vec, + ) -> Result + ``` + - Implémentation unique de l’ajout de variables, d’actions et de handlers. + - Chaque fonction existante (`build_avtransport`, `build_renderingcontrol`, `build_connectionmanager`) appelle `build_service` avec les paramètres spécifiques. + +2. **Refactorisation des macros** + - Remplacer `macro_rules! add_arg_in!` et `add_arg_out!` par des fonctions如此一来 : + ```rust + fn add_arg_in(action: &mut Action, name: &str, var: Arc) -> Result<(), FactoryError> + fn add_arg_out(action: &mut Action, name: &str, var: Arc) -> Result<(), FactoryError> + ``` + - Ces fonctions encapsulent la logique d’ajout d’arguments et centralisent la gestion d’erreur. + +3. **Mise à jour des implémentations** + - Modifier `build_avtransport`, `build_renderingcontrol`, `build_connectionmanager` pour déléguer à `build_service` et aux nouvelles fonctions d’argument. + - Vérifier que les imports restent cohérents (ajouter `use` nécessaires pour `Variable`, `Handler`, etc.). + +4. **Suppression des ancrés macros** + - Retirer les declarations `macro_rules! add_arg_in!` et `macro_rules! add_arg_out!` du fichier `renderer.rs`. + - Adapter le code appelant pour utiliser les fonctions concrètes. + +5. **Tests et CI** + - Ajouter des tests unitaires couvrant les nouvelles fonctions `build_service`, `add_arg_in`, `add_arg_out`. + - Configurer le pipeline CI pour exécuter `cargo test` et `cargo clippy` afin de détecter d’éventuelles regressions. + +6. **Documentation** + - Mettre à jour les commentaires pour refléter les nouvelles abstractions. + - Ajouter une section « Refactorisation » dans le `README` décrivant les changements. + +## Impact attendu +- **Réduction** : ~12 lignes de code redondantes éliminées. +- **Maintenabilité** : modification centralisée de la logique de construction de services. +- **Robustesse** : baisse du risque d’incohérences et de bugs liés à la duplication. +- **Lisibilité** : code plus explicite et plus proche du modèle de domaine. + +## Prochaines actions +1. Implémenter les changements proposés dans les fichiers concernés. +2. Exécuter la suite de tests pour valider la refonte. +3. Commiter les modifications après revue. + +--- +Plan finalisé. \ No newline at end of file diff --git a/.kilo/plans/1775386308232-quick-orchid.md b/.kilo/plans/1775386308232-quick-orchid.md new file mode 100644 index 00000000..03f41d39 --- /dev/null +++ b/.kilo/plans/1775386308232-quick-orchid.md @@ -0,0 +1,108 @@ +# Plan: Suppression des duplications dans `@pmowebrenderer` + +## Objectif +Éliminer les redondances de code identifiées lors de l'audit. + +--- + +## Duplication 1: Helper functions dupliquées dans `renderer.rs` + +**Fichiers affectés**: `src/renderer.rs` + +**Problème**: +- Lignes 17-54: `add_arg_in`, `add_arg_out`, `add_var`, `add_action` définies +- Lignes 150-181: Dans `build_avtransport()`, réimplémentation locale avec closures `|svc, var| { ... }` +- Répétition de 20+ appels `add_var(&mut svc, &VAR)?` et `add_action(&mut svc, Arc::new(action))?` + +**Solution**: +1. Supprimer les closures locales redéclarées (lignes 150-181) +2. Utiliser directement les fonctions helpers du haut du fichier +3. Créer une macro ou fonction utilitaire pour les appels répétés: + ```rust + macro_rules! add_vars { + ($svc:expr, $($var:expr),*) => { $({ add_var($svc, &$var)?; })* }; + } + ``` + +--- + +## Duplication 2: Pattern handlers boilerplate dans `handlers.rs` + +**Fichiers affectés**: `src/handlers.rs` + +**Problème**: +- `play_handler`, `stop_handler`, `pause_handler` (lignes 23-71): structure identique +- `next_handler`, `previous_handler` (lignes 74-95): clones +- Handlers GET (lignes 166-317): pattern `state.clone()` + `Box::pin(async move { ... set!() ... })` dupliqué + +**Solution**: +1. Créer un helper générique: + ```rust + fn make_state_handler(state: SharedState, f: F) -> ActionHandler + where F: FnOnce(&mut ActionData, &RendererState) -> Result + Send + 'static + ``` +2. Factoriser les closures `let state = state.clone()` dans chaque handler + +--- + +## Duplication 3: Extraction metadata dupliquée + +**Fichiers affectés**: `src/handlers.rs` + +**Problème**: +- Lignes 118-123: `set_uri_handler` extraction metadata +- Lignes 146-151: `set_next_uri_handler` extraction metadata (identique) + +**Solution**: +1. Extraire en fonction utilitaire: + ```rust + fn extract_metadata(data: &ActionData, key: &str) -> String { ... } + ``` + +--- + +## Duplication 4: Méthodes pipeline dans `registry.rs` + +**Fichiers affectés**: `src/registry.rs` + +**Problème**: +- `send_pipeline_command` (lignes 272-279) appelle `get_pipeline` (lignes 281-283) +- `load_uri` (lignes 286-290), `send_play_command` (lignes 293-296), `send_pause_command` (lignes 299-301) sont des wrappers quasi-identiques + +**Solution**: +Consolider en méthodes génériques: +```rust +pub async fn send_command(&self, instance_id: &str, cmd: PipelineControl) { + if let Some(pipeline) = self.get_pipeline(instance_id) { + pipeline.send(cmd).await; + } +} +``` + +--- + +## Duplication 5: Feature flags avec code dupliqué + +**Fichiers affectés**: `src/registry.rs` + +**Problème**: +- Lignes 51-68 et 334-395: double impl de `create_instance` selon feature + +**Solution**: +- Extraire la logique commune dans une fonction privée +- Utiliser `#[cfg]` seulement pour les différences (appel à pmoserver) + +--- + +## Ordre de traitement suggéré + +1. **Phase 1**: Helpers dans `renderer.rs` (les plus simples) +2. **Phase 2**: Handlers dans `handlers.rs` (plus complexe, nécessite macro) +3. **Phase 3**: Méthodes pipeline dans `registry.rs` +4. **Phase 4**: Feature flags + +## Vérification +Après chaque phase, exécuter: +```bash +cargo check --package pmowebrenderer +``` \ No newline at end of file diff --git a/Blackboard/Todo/patch_position_info_for_stream.md b/Blackboard/Done/patch_position_info_for_stream.md similarity index 100% rename from Blackboard/Todo/patch_position_info_for_stream.md rename to Blackboard/Done/patch_position_info_for_stream.md diff --git a/Blackboard/ToThinkAbout/webrenderer.md b/Blackboard/ToThinkAbout/webrenderer.md index 07aab8ce..af6fc291 100644 --- a/Blackboard/ToThinkAbout/webrenderer.md +++ b/Blackboard/ToThinkAbout/webrenderer.md @@ -1,138 +1,443 @@ -Parfait. Voici un **schéma fonctionnel minimal** pour un **MediaRenderer UPnP privé par navigateur** avec **token**. L’idée est de rester fidèle à ton backend Rust existant et à la webapp Vue.js. En s'appuyant sur l'architecture de PMOMusic, j'aimerais que tu proposes un plan détaillé pour implémenter un tel système de Média Renderer. +# Web Media Renderer - Architecture -- L'application web se trouve dans: [@webapp](file:///Users/coissac/Sync/maison/Petite_maisons/src/pmomusic/pmoapp/webapp) -- Tu as un prototype de Média Renderer dans: [@pmomediarenderer](file:///Users/coissac/Sync/maison/Petite_maisons/src/pmomusic/pmomediarenderer) -- Le contrôle point est dans : [@pmocontrol](file:///Users/coissac/Sync/maison/Petite_maisons/src/pmomusic/pmocontrol) -- Tu implémenteras ce nouveau système de Média Renderer dans la CRATe pmowebrenderer +## Vision -Tu mettras une version du plan en Markdown dans le répertoire [@Architecture](file:///Users/coissac/Sync/maison/Petite_maisons/src/pmomusic/Blackboard/Architecture) . +Système de Media Renderer pilotable à distance via UPnP, exposant un flux audio vers différents types de lecteurs physiques. ---- +## Architecture globale en 4 parties -## 1. Flow général - -``` -Browser (Vue.js Control Point) - ┌───────────────┐ - │ UI / audio │ - │ WebSocket │ - └───────▲───────┘ - │ token - │ - ▼ -Rust backend (UPnP MediaRenderer) - ┌───────────────────────────┐ - │ Token → Renderer mapping │ - │ Device XML / SOAP endpoints│ - │ Play/Pause/Stop → WS → Browser │ - └───────────────────────────┘ +```mermaid +flowchart LR + A[Media Server] -->|flux audio| B[Control Point] + B -->|commandes| C[Web Media Renderer] + C -->|flux + contrôles| D[Device physique] + + subgraph Devices physiques + D1[Browser] + D2[Android Auto] + D3[Apple CarPlay] + D4[Sonos multipoint] + D5[Chromecast] + end + + D --> D1 + D --> D2 + D --> D3 + D --> D4 + D --> D5 ``` ---- +### Rôles -## 2. Étapes détaillées +1. **Media Server** - Source audio (le flux OGG-FLAC existant) +2. **Control Point** - Interface UI qui envoie les commandes (pause, play, seek, next, prev) +3. **Web Media Renderer** - Hub qui expose le flux et traduit les commandes selon le device +4. **Physical Device** - Lecteur final (browser, voiture, Sonos, Chromecast...) -### a) Création du renderer +## Web Media Renderer - Rôle central -1. Le navigateur se connecte via WebSocket ou HTTP. -2. Rust génère un token unique pour ce client : - - ```rust - use uuid::Uuid; - let token = Uuid::new_v4().to_string(); - ``` -3. Rust crée une instance MediaRenderer **privée**, associée à ce token : - - * Device description XML : `/renderer//desc.xml` - * AVTransport SOAP : `/renderer//avtransport` - * RenderingControl SOAP : `/renderer//renderingcontrol` - ---- - -### b) Control Point - -* La webapp Vue.js reçoit le token et la “déclare” au Control Point : - -```js -const renderer = { - token: "abcd-1234-efgh", - name: "Browser Renderer" -}; - -// Ajout au control point local -controlPoint.addRenderer(renderer); -``` - -* Toutes les commandes Play/Pause/Stop incluent ce token : - -```js -ws.send(JSON.stringify({ - token: renderer.token, - action: "play", - uri: "http://localhost:8080/media.mp3" -})); -``` - ---- - -### c) Backend Rust : dispatcher les commandes - -* Rust reçoit le JSON avec le token. -* Vérifie que le token correspond à un renderer actif. -* Transmet la commande au navigateur via WebSocket (ou HTTP push) : - -```rust -match msg.action.as_str() { - "play" => send_ws_to_browser(&token, format!("play:{}", msg.uri)), - "pause" => send_ws_to_browser(&token, "pause".to_string()), - "stop" => send_ws_to_browser(&token, "stop".to_string()), - _ => (), +```mermaid +blockdiag +{ + block = Commandes UPnP + block -> "Web Media Renderer" -> Adaptation selon device + "Web Media Renderer" -> Device-specific protocols } ``` -* Rust met à jour l’état du renderer (AVTransport/RenderingControl) pour le Control Point. +### Rôle central: Adaptateur ---- +Le Web Media Renderer est un **adaptateur** qui: +- **Reçoit le flux** du Media Server (OGG-FLAC) +- **Reçoit les commandes** du Control Point (UPnP) +- **Les traduit** vers les devices physiques +- **Expose une API de contrôle** commune -### d) Lecture côté navigateur +### Ce qui est COMMUN (factorisé) -* Le navigateur reçoit la commande via WebSocket et pilote `