From 23705c9c2114fc2d184d93499512bc65711be543 Mon Sep 17 00:00:00 2001 From: Eric Coissac Date: Wed, 3 Dec 2025 21:34:23 +0100 Subject: [PATCH] =?UTF-8?q?Eh=20bien,=20je=20ne=20sais=20plus=20=C3=A0=20q?= =?UTF-8?q?uoi=20=C3=A7a=20correspond.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 1 + PMOMusic/Cargo.toml | 1 + PMOMusic/src/main.rs | 10 +++ .../examples/pmomusic_integration_example.rs | 72 +++++++++++++++++ pmocontrol/src/lib.rs | 3 + pmocontrol/src/pmoserver_ext.rs | 80 ++++++++++++++++++- 6 files changed, 165 insertions(+), 2 deletions(-) create mode 100644 pmocontrol/examples/pmomusic_integration_example.rs diff --git a/Cargo.lock b/Cargo.lock index 9643d4b4..ac7b4d44 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12,6 +12,7 @@ dependencies = [ "pmoaudio-ext", "pmoaudiocache", "pmoconfig", + "pmocontrol", "pmocovers", "pmomediarenderer", "pmomediaserver", diff --git a/PMOMusic/Cargo.toml b/PMOMusic/Cargo.toml index d31b3de9..b17201d9 100644 --- a/PMOMusic/Cargo.toml +++ b/PMOMusic/Cargo.toml @@ -14,6 +14,7 @@ pmocovers = { path = "../pmocovers", features = ["pmoserver"] } pmoaudiocache = { path = "../pmoaudiocache", features = ["pmoserver"]} pmoaudio-ext = { path = "../pmoaudio-ext", features = ["all"] } pmoapp = { path = "../pmoapp", features = ["pmoserver"] } +pmocontrol = { path = "../pmocontrol", features = ["pmoserver"] } tokio = { version = "1.35", features = ["rt-multi-thread", "macros", "sync", "time","signal"] } tracing = "0.1.41" diff --git a/PMOMusic/src/main.rs b/PMOMusic/src/main.rs index e4892c63..4463e41c 100644 --- a/PMOMusic/src/main.rs +++ b/PMOMusic/src/main.rs @@ -1,4 +1,5 @@ use pmoapp::{WebAppExt, Webapp}; +use pmocontrol::ControlPointExt; use pmomediarenderer::MEDIA_RENDERER; use pmomediaserver::{ MEDIA_SERVER, MediaServerDeviceExt, ParadiseStreamingExt, sources::SourcesExt, @@ -97,6 +98,15 @@ async fn main() -> Result<(), Box> { server_instance.description_route() ); + // Enregistrer le Control Point (découverte renderers/serveurs + API REST + SSE) + info!("🎛️ Registering Control Point..."); + let _control_point = server + .write() + .await + .register_control_point(5) + .await + .expect("Failed to register Control Point"); + // Ajouter la webapp via le trait WebAppExt info!("📡 Registering Web application..."); server diff --git a/pmocontrol/examples/pmomusic_integration_example.rs b/pmocontrol/examples/pmomusic_integration_example.rs new file mode 100644 index 00000000..ad395589 --- /dev/null +++ b/pmocontrol/examples/pmomusic_integration_example.rs @@ -0,0 +1,72 @@ +//! Exemple d'intégration du Control Point dans PMOMusic +//! +//! Cet exemple montre comment enregistrer le Control Point dans une application +//! PMOMusic complète, en suivant le même pattern que les autres composants. + +use pmocontrol::ControlPointExt; +use pmoserver::Server; +use tracing::info; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Initialiser le logging + tracing_subscriber::fmt::init(); + + // ========== PHASE 1 : Infrastructure ========== + + let server = Server::create_upnp_server().await?; + + // ========== PHASE 2 : Enregistrement des composants ========== + + // Enregistrer les devices UPnP, sources musicales, etc. + // (code existant de PMOMusic...) + + // ========== Enregistrer le Control Point ========== + // + // Cette ligne unique : + // 1. Lance le runtime SSDP et la découverte des devices + // 2. Démarre le polling des renderers (état, position, volume, etc.) + // 3. S'abonne aux événements UPnP des serveurs de médias + // 4. Enregistre toutes les routes REST (/api/control/*) + // 5. Enregistre tous les endpoints SSE (/api/control/events/*) + // 6. Génère la documentation OpenAPI + + info!("🎛️ Registering Control Point..."); + let control_point = server + .write() + .await + .register_control_point(5) // timeout de 5 secondes pour les requêtes HTTP + .await?; + + // Le Control Point est maintenant actif ! + // On peut l'utiliser directement si besoin + info!("✅ Control Point ready!"); + + // Exemple : lister les renderers découverts (optionnel) + tokio::time::sleep(tokio::time::Duration::from_secs(3)).await; + let renderers = control_point.list_music_renderers(); + info!("📻 Discovered {} renderer(s)", renderers.len()); + for renderer in renderers { + info!(" - {} ({})", renderer.friendly_name, renderer.id.0); + } + + // ========== PHASE 3 : Démarrage du serveur ========== + + info!("🌐 Starting HTTP server..."); + server.write().await.start().await; + + info!("✅ PMOMusic is ready!"); + info!("📡 Control Point API available at:"); + info!(" - GET /api/control/renderers"); + info!(" - GET /api/control/servers"); + info!(" - GET /api/control/events (SSE)"); + info!(" - GET /api/control/events/renderers (SSE)"); + info!(" - GET /api/control/events/servers (SSE)"); + info!(" - Docs: /swagger-ui/control"); + info!(""); + info!("Press Ctrl+C to stop..."); + + server.write().await.wait().await; + + Ok(()) +} diff --git a/pmocontrol/src/lib.rs b/pmocontrol/src/lib.rs index ac3a205a..d816aac8 100644 --- a/pmocontrol/src/lib.rs +++ b/pmocontrol/src/lib.rs @@ -26,6 +26,9 @@ pub mod pmoserver_ext; #[cfg(feature = "pmoserver")] pub mod sse; +#[cfg(feature = "pmoserver")] +pub use pmoserver_ext::ControlPointExt; + pub use arylic_tcp::ArylicTcpRenderer; pub use avtransport_client::{AvTransportClient, PositionInfo, TransportInfo}; pub use capabilities::{ diff --git a/pmocontrol/src/pmoserver_ext.rs b/pmocontrol/src/pmoserver_ext.rs index 7f62eec3..8e479010 100644 --- a/pmocontrol/src/pmoserver_ext.rs +++ b/pmocontrol/src/pmoserver_ext.rs @@ -962,7 +962,13 @@ pub fn create_api_router(state: ControlPointState, control_point: Arc std::io::Result>; + + /// Initialise l'API Control Point (bas niveau) + /// + /// Cette méthode est appelée automatiquement par `register_control_point()`. + /// Utilisez `register_control_point()` pour la plupart des cas d'usage. + /// + /// # Routes créées + /// + /// - API REST: `/api/control/*` + /// - SSE Events: `/api/control/events/*` + /// - Swagger: `/swagger-ui/control` + /// + /// # Arguments + /// + /// * `control_point` - Instance du ControlPoint déjà créée async fn init_control_point(&mut self, control_point: Arc); } #[cfg(feature = "pmoserver")] #[async_trait] impl ControlPointExt for pmoserver::Server { + async fn register_control_point(&mut self, timeout_secs: u64) -> std::io::Result> { + use tracing::info; + + info!("🎛️ Initializing Control Point..."); + + // 1. Lancer le runtime du ControlPoint + let control_point = ControlPoint::spawn(timeout_secs)?; + let control_point = Arc::new(control_point); + + info!("✅ Control Point runtime started"); + info!(" - SSDP discovery active"); + info!(" - Renderer polling active (1s interval)"); + info!(" - MediaServer event subscriptions active"); + + // 2. Enregistrer les routes HTTP REST et SSE + self.init_control_point(control_point.clone()).await; + + info!("✅ Control Point API registered:"); + info!(" - REST API: /api/control/*"); + info!(" - SSE Events: /api/control/events/*"); + info!(" - OpenAPI docs: /swagger-ui/control"); + + Ok(control_point) + } + async fn init_control_point(&mut self, control_point: Arc) { let state = ControlPointState::new(control_point.clone());