use pmoapp::{WebAppExt, Webapp}; use pmocontrol::ControlPointExt; use pmomediaserver::{ MEDIA_SERVER, MediaServerDeviceExt, ParadiseStreamingExt, sources::SourcesExt, }; use pmoserver::Server; use pmosource::MusicSourceExt; use pmoupnp::UpnpServerExt; use pmowebrenderer::WebRendererExt; use tracing::info; #[tokio::main] async fn main() -> Result<(), Box> { // ========== PHASE 1 : Infrastructure UPnP ========== // #[cfg(tokio_unstable)] // console_subscriber::init(); let server = Server::create_upnp_server().await?; // Routes personnalisées de l'application server .write() .await .add_route("/info", || async { serde_json::json!({"version": "1.0.0"}) }) .await; // Initialiser le système de gestion des sources musicales avec API REST info!("📡 Initializing music sources management system..."); server .write() .await .init_music_sources() .await .expect("Failed to initialize music sources API"); // ========== PHASE 2 : Configuration métier ========== // Enregistrer les sources musicales info!("🎵 Registering music sources..."); // Enregistrer Qobuz pour activer les lazy providers (QOBUZ:PK) if let Err(e) = server.write().await.register_qobuz().await { tracing::warn!("⚠️ Failed to register Qobuz source: {}", e); } // Initialiser les canaux de streaming Radio Paradise (pipelines + routes HTTP) info!("📻 Initializing Radio Paradise streaming channels..."); if let Err(e) = server.write().await.init_paradise_streaming().await { tracing::warn!("⚠️ Failed to initialize Paradise streaming: {}", e); } else { // Enregistrer la source Radio Paradise UPnP (inclut l'initialisation de l'API) if let Err(e) = server.write().await.register_paradise().await { tracing::warn!("⚠️ Failed to register Radio Paradise source: {}", e); } } // Enregistrer la source Radio France (inclut l'initialisation des routes API) info!("📻 Registering Radio France source..."); if let Err(e) = server.write().await.register_radiofrance().await { tracing::warn!("⚠️ Failed to register Radio France source: {}", e); } // Enregistrer la source URL / Partage info!("🔗 Registering URL source..."); if let Err(e) = server.write().await.register_urlsource().await { tracing::warn!("⚠️ Failed to register URL source: {}", e); } // Lister toutes les sources enregistrées let sources = server.read().await.list_music_sources().await; info!("✅ {} music source(s) registered", sources.len()); for source in sources { info!(" - {} ({})", source.name(), source.id()); } // Enregistrer les devices UPnP (HTTP + SSDP automatique) info!("📡 Registering UPnP devices..."); let server_instance = server .write() .await .register_device(MEDIA_SERVER.clone(), true) .await .expect("Failed to register MediaServer"); // Enregistrer l'instance ContentDirectory pour les notifications GENA if let Some(cd_service) = server_instance.get_service("ContentDirectory") { pmomediaserver::contentdirectory::state::register_instance(&cd_service); } // Initialiser les ProtocolInfo du MediaServer server_instance.init_protocol_info(); info!( "✅ MediaServer ready at {}{}", server_instance.base_url(), 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"); // Enregistrer le WebRenderer (endpoint WebSocket pour renderers navigateur) info!("🌐 Registering WebRenderer..."); server .write() .await .register_web_renderer(control_point) .await .expect("Failed to register WebRenderer"); // Ajouter la webapp via le trait WebAppExt info!("📡 Registering Web application..."); server .write() .await .add_webapp_with_redirect::("/app") .await; // ========== PHASE 3 : Démarrage du serveur ========== info!("🌐 Starting HTTP server..."); server.write().await.start().await; info!("✅ PMOMusic is ready!"); info!("Press Ctrl+C to stop..."); // Extraire le join_handle AVANT de libérer le write lock, // pour pouvoir l'awaiter sans tenir le write lock du serveur global. // (Tenir le write lock pendant wait() bloquerait register_device() dynamique) let join_handle = server.write().await.take_join_handle(); if let Some(h) = join_handle { let _ = h.await; } // Le serveur HTTP est arrêté, mais des threads (ControlPoint, etc.) peuvent encore tourner // Attendre 2 secondes pour laisser le temps aux threads de se terminer info!("Waiting for background threads to finish..."); tokio::time::sleep(std::time::Duration::from_secs(2)).await; // Forcer l'arrêt du processus (les threads du ControlPoint tournent en boucle infinie) info!("✅ PMOMusic stopped"); std::process::exit(0); }