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
5.7 KiB
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
- Le navigateur ouvre une WebSocket vers
/api/webrenderer/ws - Il envoie un message
Initavec ses capabilities (user_agent, formats supportes) - Le backend construit un
DeviceUPnP avec desServicemodels custom :- AVTransport (Play, Stop, Pause, Seek, SetURI, GetPositionInfo, etc.)
- RenderingControl (SetVolume, GetVolume, SetMute, GetMute)
- ConnectionManager (GetProtocolInfo)
- Chaque Action a un handler qui capture le
mpsc::UnboundedSender<ServerMessage>du WS - Le device est enregistre via
Server::register_device()(routes SOAP + DEVICE_REGISTRY) - Un
RendererInfoest pousse dans leDeviceRegistrydu ControlPoint viapush_renderer() - Le backend renvoie un
SessionCreatedavec 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
StateVariablestatics de pmomediarenderer sont reutilisees viaArc::clone(&*VAR) - De nouvelles
Actionsont creees avecAction::new(), configurees avecset_handler()puis wrappees enArc - Les handlers capturent le sender WS et le
SharedState(clone a chaque appel viaFnclosure) - Le
Deviceest 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 :
- Lisent les arguments SOAP depuis
ActionDatavia la macroget!() - Envoient un
ServerMessage::CommandouSetVolume/SetMutevia le canal mpsc - Mettent a jour le
SharedStatelocal - Retournent les arguments OUT via
set!()si necessaire
Navigateur -> UPnP (etats)
Quand le navigateur envoie StateUpdate, PositionUpdate, MetadataUpdate ou VolumeUpdate :
- Le
SharedStateest mis a jour - Les
StateVarInstanceduDeviceInstancesont mises a jour viaset_value(StateValue::...) - 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
SessionManagergere 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
SessionManagerverifie 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
-
parking_lot::RwLockWriteGuard non-Send : les guards de
SharedStatedoivent etre dropes avant tout.awaitdans les handlers async. Utiliser des blocs{ ... }pour limiter la portee. -
Fn vs FnOnce : les
ActionHandlersontFn(appeles plusieurs fois). Les closures doivent clonerwsetstatea chaque appel, avant leasync move. -
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.
-
Acces au Server : le WebSocket handler utilise
pmoserver::get_server()(singleton global) pour enregistrer les devices dynamiquement.