feat: implémentation du WebRenderer UPnP privé par navigateur
Ajout de la fonctionnalité WebRenderer permettant à chaque navigateur connecté de devenir un MediaRenderer UPnP privé. - Création du crate pmowebrenderer avec l'architecture complète - Implémentation des handlers SOAP → WebSocket pour les services AVTransport, RenderingControl et ConnectionManager - Intégration avec le ControlPoint pour l'enregistrement dynamique des renderers - Gestion des sessions avec timeout et cleanup automatique - Support des commandes de transport (play, pause, stop, seek) et du contrôle du volume - Mise à jour des dépendances dans Cargo.toml et Cargo.lock - Documentation de l'architecture dans Blackboard/Architecture/webrenderer.md
This commit is contained in:
97
Blackboard/Architecture/webrenderer.md
Normal file
97
Blackboard/Architecture/webrenderer.md
Normal file
@@ -0,0 +1,97 @@
|
||||
# WebRenderer UPnP privé par navigateur
|
||||
|
||||
## Vue d'ensemble
|
||||
|
||||
Le crate `pmowebrenderer` transforme chaque navigateur connecté en un **MediaRenderer UPnP privé**. Quand un navigateur se connecte via WebSocket, le backend Rust crée dynamiquement un device UPnP dédié. Le ControlPoint envoie des commandes SOAP à ce device, et les action handlers les relaient au navigateur via WebSocket. Le navigateur joue l'audio via `<audio>` et renvoie l'état au backend.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
Browser (Vue.js) Rust Backend ControlPoint
|
||||
| | |
|
||||
|-- WS connect --------------->| |
|
||||
|<-- SessionCreated (token) ---| |
|
||||
|-- Init (capabilities) ------>| |
|
||||
| |-- register_device() -------->| (Server)
|
||||
| | (Device + Services custom) |
|
||||
| |-- push_renderer() ---------->| (CP registry)
|
||||
| | |
|
||||
| |<-- SOAP Play (control_handler)
|
||||
|<-- Command(Play, uri) -------| (action handler -> WS) |
|
||||
|-- StateUpdate(Playing) ----->| |
|
||||
| |-- update StateVarInstance -->| (evented -> SSE)
|
||||
| | |
|
||||
|-- WS disconnect ------------>| |
|
||||
| |-- device_says_byebye() ----->| (CP registry)
|
||||
```
|
||||
|
||||
## Flux de connexion
|
||||
|
||||
1. Le navigateur ouvre une WebSocket vers `/api/webrenderer/ws`
|
||||
2. Il envoie un message `Init` avec ses capabilities (user_agent, formats supportes)
|
||||
3. Le backend construit un `Device` UPnP avec des `Service` models custom :
|
||||
- AVTransport (Play, Stop, Pause, Seek, SetURI, GetPositionInfo, etc.)
|
||||
- RenderingControl (SetVolume, GetVolume, SetMute, GetMute)
|
||||
- ConnectionManager (GetProtocolInfo)
|
||||
4. Chaque Action a un handler qui capture le `mpsc::UnboundedSender<ServerMessage>` du WS
|
||||
5. Le device est enregistre via `Server::register_device()` (routes SOAP + DEVICE_REGISTRY)
|
||||
6. Un `RendererInfo` est pousse dans le `DeviceRegistry` du ControlPoint via `push_renderer()`
|
||||
7. Le backend renvoie un `SessionCreated` avec le token et les infos du renderer
|
||||
|
||||
## Decision cle : Services dynamiques (zero changement pmoupnp)
|
||||
|
||||
Plutot que de modifier pmoupnp pour permettre l'override de handlers post-creation, on **construit des `Service` models dynamiques** pour chaque session WebSocket :
|
||||
|
||||
- Les `StateVariable` statics de pmomediarenderer sont reutilisees via `Arc::clone(&*VAR)`
|
||||
- De nouvelles `Action` sont creees avec `Action::new()`, configurees avec `set_handler()` puis wrappees en `Arc`
|
||||
- Les handlers capturent le sender WS et le `SharedState` (clone a chaque appel via `Fn` closure)
|
||||
- Le `Device` est construit avec ces services custom, puis enregistre normalement
|
||||
|
||||
Cela reutilise toute l'infrastructure existante sans modification de pmoupnp ni pmomediarenderer.
|
||||
|
||||
## Propagation d'etat bidirectionnelle
|
||||
|
||||
### SOAP -> Navigateur (commandes)
|
||||
Les action handlers des services AVTransport/RenderingControl :
|
||||
1. Lisent les arguments SOAP depuis `ActionData` via la macro `get!()`
|
||||
2. Envoient un `ServerMessage::Command` ou `SetVolume`/`SetMute` via le canal mpsc
|
||||
3. Mettent a jour le `SharedState` local
|
||||
4. Retournent les arguments OUT via `set!()` si necessaire
|
||||
|
||||
### Navigateur -> UPnP (etats)
|
||||
Quand le navigateur envoie `StateUpdate`, `PositionUpdate`, `MetadataUpdate` ou `VolumeUpdate` :
|
||||
1. Le `SharedState` est mis a jour
|
||||
2. Les `StateVarInstance` du `DeviceInstance` sont mises a jour via `set_value(StateValue::...)`
|
||||
3. Les variables evented declenchent les notifications UPnP captees par le watcher du ControlPoint
|
||||
|
||||
## Cycle de vie
|
||||
|
||||
- **Connexion** : creation du Device, enregistrement aupres du Server et du ControlPoint
|
||||
- **Session active** : le `SessionManager` gere un timeout de 30 minutes d'inactivite
|
||||
- **Deconnexion WS** : appel a `device_says_byebye()` sur le registry du ControlPoint pour marquer offline
|
||||
- **Cleanup automatique** : le `SessionManager` verifie toutes les 60 secondes les sessions expirees
|
||||
- **Pas de SSDP** : les WebRenderers sont injectes directement, max_age de 86400s
|
||||
|
||||
## Structure des fichiers
|
||||
|
||||
| Fichier | Role |
|
||||
|---------|------|
|
||||
| `handlers.rs` | Action handlers SOAP->WS (play, stop, pause, seek, set_uri, get_*_info, volume, mute) |
|
||||
| `renderer.rs` | `WebRendererFactory` : construction dynamique Device/Services avec handlers |
|
||||
| `websocket.rs` | Handler WS : connexion, reception messages, creation device, propagation etat |
|
||||
| `session.rs` | `SessionManager` : gestion des sessions avec timeout |
|
||||
| `config.rs` | `WebRendererExt` trait pour `pmoserver::Server` (enregistrement route WS) |
|
||||
| `messages.rs` | Types de messages WS (ServerMessage, ClientMessage, etc.) |
|
||||
| `state.rs` | `RendererState` et `SharedState` (etat partage entre handlers et WS) |
|
||||
| `error.rs` | Types d'erreur du crate |
|
||||
| `lib.rs` | Exports publics |
|
||||
|
||||
## Points d'attention
|
||||
|
||||
1. **parking_lot::RwLockWriteGuard non-Send** : les guards de `SharedState` doivent etre dropes avant tout `.await` dans les handlers async. Utiliser des blocs `{ ... }` pour limiter la portee.
|
||||
|
||||
2. **Fn vs FnOnce** : les `ActionHandler` sont `Fn` (appeles plusieurs fois). Les closures doivent cloner `ws` et `state` a chaque appel, avant le `async move`.
|
||||
|
||||
3. **Routes Axum persistantes** : Axum ne supporte pas la suppression de routes. Les routes SOAP d'un device deconnecte persistent mais les handlers retournent des erreurs naturellement.
|
||||
|
||||
4. **Acces au Server** : le WebSocket handler utilise `pmoserver::get_server()` (singleton global) pour enregistrer les devices dynamiquement.
|
||||
@@ -5,7 +5,7 @@ Parfait. Voici un **schéma fonctionnel minimal** pour un **MediaRenderer UPnP p
|
||||
- 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
|
||||
|
||||
Tu mettras une version du plan en Markdown dans le répertoire Architecture.
|
||||
Tu mettras une version du plan en Markdown dans le répertoire [@Architecture](file:///Users/coissac/Sync/maison/Petite_maisons/src/pmomusic/Blackboard/Architecture) .
|
||||
|
||||
---
|
||||
|
||||
@@ -136,4 +136,3 @@ ws.onmessage = (evt) => {
|
||||
---
|
||||
|
||||
💡 Ce modèle est très proche de ce que font **Mopidy avec Iris**, **Kodi Remote**, ou **Chromecast / local cast** : le device est connu et dédié, pas besoin de découverte réseau.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user