Faire fonctionner le media server
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -2509,6 +2509,7 @@ dependencies = [
|
||||
"thiserror 2.0.17",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"url",
|
||||
"utoipa",
|
||||
"uuid",
|
||||
|
||||
@@ -1,51 +1,23 @@
|
||||
use pmoapp::{WebAppExt, Webapp};
|
||||
use pmocovers::CoverCacheExt;
|
||||
use pmoaudiocache::AudioCacheExt;
|
||||
use pmomediarenderer::MEDIA_RENDERER;
|
||||
use pmomediaserver::{MEDIA_SERVER, sources::SourcesExt};
|
||||
use pmosource::MusicSourceExt;
|
||||
use pmoserver::ServerBuilder;
|
||||
use pmoupnp::{UpnpServerExt, ssdp::SsdpServer, upnp_api::UpnpApiExt};
|
||||
use pmoserver::Server;
|
||||
use pmoupnp::{UpnpServerExt, upnp_api::UpnpApiExt};
|
||||
use tracing::info;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// Créer le serveur - le trait UpnpServer étend automatiquement Server
|
||||
let mut server = ServerBuilder::new_configured().build();
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// ========== PHASE 1 : Infrastructure UPnP ==========
|
||||
let mut server = Server::create_upnp_server().await?;
|
||||
|
||||
// Initialiser le logging et enregistrer les routes de logs
|
||||
server.init_logging().await;
|
||||
|
||||
info!("📡 Registering the cover cache...");
|
||||
let covercache = server
|
||||
.init_cover_cache_configured()
|
||||
.await
|
||||
.expect("Cannot initialise the image cache");
|
||||
|
||||
info!("✅ Cover cache ready at {}", covercache.cache_dir().display());
|
||||
|
||||
info!("📡 Registering the audio cache...");
|
||||
let audiocache = server
|
||||
.init_audio_cache_configured()
|
||||
.await
|
||||
.expect("Cannot initialise the audio cache");
|
||||
|
||||
info!("✅ Audio cache ready at {}", audiocache.cache_dir().display());
|
||||
|
||||
// Routes de base
|
||||
// Routes personnalisées de l'application
|
||||
server
|
||||
.add_route("/info", || async {
|
||||
serde_json::json!({"version": "1.0.0"})
|
||||
})
|
||||
.await;
|
||||
|
||||
// Ajouter la webapp via le trait WebAppExt
|
||||
info!("📡 Registering Web application...");
|
||||
server.add_webapp_with_redirect::<Webapp>("/app").await;
|
||||
|
||||
// Enregistrer l'API d'introspection UPnP
|
||||
server.register_upnp_api().await;
|
||||
|
||||
// Initialiser le système de gestion des sources musicales avec API REST
|
||||
info!("📡 Initializing music sources management system...");
|
||||
server
|
||||
@@ -53,29 +25,19 @@ async fn main() {
|
||||
.await
|
||||
.expect("Failed to initialize music sources API");
|
||||
|
||||
info!("📡 Registering MediaRenderer...");
|
||||
let renderer_instance = server
|
||||
.register_device(MEDIA_RENDERER.clone())
|
||||
.await
|
||||
.expect("Failed to register MediaRenderer routes");
|
||||
|
||||
info!(
|
||||
"✅ MediaRenderer ready at {}{}",
|
||||
renderer_instance.base_url(),
|
||||
renderer_instance.description_route()
|
||||
);
|
||||
// ========== PHASE 2 : Configuration métier ==========
|
||||
|
||||
// Enregistrer les sources musicales
|
||||
info!("📡 Registering music sources...");
|
||||
info!("🎵 Registering music sources...");
|
||||
|
||||
// Enregistrer Qobuz
|
||||
if let Err(e) = server.register_qobuz().await {
|
||||
tracing::warn!("Failed to register Qobuz: {}", e);
|
||||
}
|
||||
// // Enregistrer Qobuz
|
||||
// if let Err(e) = server.register_qobuz().await {
|
||||
// tracing::warn!("⚠️ Failed to register Qobuz: {}", e);
|
||||
// }
|
||||
|
||||
// Enregistrer Radio Paradise
|
||||
if let Err(e) = server.register_paradise().await {
|
||||
tracing::warn!("Failed to register Radio Paradise: {}", e);
|
||||
tracing::warn!("⚠️ Failed to register Radio Paradise: {}", e);
|
||||
}
|
||||
|
||||
// Lister toutes les sources enregistrées
|
||||
@@ -85,11 +47,24 @@ async fn main() {
|
||||
info!(" - {} ({})", source.name(), source.id());
|
||||
}
|
||||
|
||||
info!("📡 Registering MediaServer...");
|
||||
// Enregistrer les devices UPnP (HTTP + SSDP automatique)
|
||||
info!("📡 Registering UPnP devices...");
|
||||
|
||||
let renderer_instance = server
|
||||
.register_device(MEDIA_RENDERER.clone())
|
||||
.await
|
||||
.expect("Failed to register MediaRenderer");
|
||||
|
||||
info!(
|
||||
"✅ MediaRenderer ready at {}{}",
|
||||
renderer_instance.base_url(),
|
||||
renderer_instance.description_route()
|
||||
);
|
||||
|
||||
let server_instance = server
|
||||
.register_device(MEDIA_SERVER.clone())
|
||||
.await
|
||||
.expect("Failed to register MediaServer routes");
|
||||
.expect("Failed to register MediaServer");
|
||||
|
||||
info!(
|
||||
"✅ MediaServer ready at {}{}",
|
||||
@@ -97,16 +72,18 @@ async fn main() {
|
||||
server_instance.description_route()
|
||||
);
|
||||
|
||||
// Créer et démarrer le serveur SSDP
|
||||
info!("📡 Starting SSDP discovery...");
|
||||
let mut ssdp_server = SsdpServer::new();
|
||||
ssdp_server.start().expect("Failed to start SSDP server");
|
||||
// Ajouter la webapp via le trait WebAppExt
|
||||
info!("📡 Registering Web application...");
|
||||
server.add_webapp_with_redirect::<Webapp>("/app").await;
|
||||
|
||||
// Créer et enregistrer le device SSDP pour le MediaRenderer
|
||||
let ssdp_device = renderer_instance.to_ssdp_device("PMOMusic", "1.0");
|
||||
ssdp_server.add_device(ssdp_device);
|
||||
info!("✅ SSDP announcements sent for MediaRenderer");
|
||||
// ========== PHASE 3 : Démarrage du serveur ==========
|
||||
|
||||
info!("🌐 Starting HTTP server...");
|
||||
server.start().await;
|
||||
|
||||
info!("✅ PMOMusic is ready!");
|
||||
info!("Press Ctrl+C to stop...");
|
||||
server.wait().await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ chrono = { version = "0.4.42", features = ["serde"] }
|
||||
once_cell = "1.20"
|
||||
parking_lot = "0.12"
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = "0.3"
|
||||
bevy_reflect = "0.17.1"
|
||||
bevy_reflect_derive = "0.17.1"
|
||||
reqwest = "0.12.23"
|
||||
|
||||
@@ -29,6 +29,8 @@ use crate::devices::errors::DeviceError;
|
||||
use crate::devices::{Device, DeviceInstance, DeviceRegistry};
|
||||
use crate::UpnpModel;
|
||||
use crate::cache_registry::CACHE_REGISTRY;
|
||||
use crate::ssdp::SsdpServer;
|
||||
use crate::upnp_api::UpnpApiExt;
|
||||
|
||||
use pmocovers::Cache as CoverCache;
|
||||
use pmoaudiocache::Cache as AudioCache;
|
||||
@@ -42,6 +44,14 @@ static DEVICE_REGISTRY: Lazy<RwLock<DeviceRegistry>> = Lazy::new(|| {
|
||||
RwLock::new(DeviceRegistry::new())
|
||||
});
|
||||
|
||||
/// Serveur SSDP global et thread-safe.
|
||||
///
|
||||
/// Utilise Lazy pour une initialisation paresseuse et RwLock pour le partage entre threads.
|
||||
/// Permet l'annonce automatique des devices UPnP sur le réseau.
|
||||
static SSDP_SERVER: Lazy<RwLock<Option<SsdpServer>>> = Lazy::new(|| {
|
||||
RwLock::new(None)
|
||||
});
|
||||
|
||||
/// Trait pour étendre un serveur avec des fonctionnalités UPnP.
|
||||
///
|
||||
/// Ce trait ajoute :
|
||||
@@ -147,11 +157,70 @@ pub trait UpnpServerExt {
|
||||
|
||||
/// Récupérer le cache audio
|
||||
fn audio_cache(&self) -> Option<Arc<AudioCache>>;
|
||||
|
||||
// ========= SSDP Management (NOUVEAU) =========
|
||||
|
||||
/// Initialise et démarre le serveur SSDP
|
||||
///
|
||||
/// Cette méthode crée et démarre le serveur SSDP qui gère les annonces
|
||||
/// UPnP sur le réseau (NOTIFY alive/byebye, réponses M-SEARCH).
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// `Ok(())` si l'initialisation réussit, `Err` sinon.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Cette méthode peut être appelée plusieurs fois sans effet si SSDP
|
||||
/// est déjà initialisé.
|
||||
fn init_ssdp(&self) -> Result<(), std::io::Error>;
|
||||
|
||||
/// Vérifie si le serveur SSDP est initialisé
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// `true` si SSDP est actif, `false` sinon
|
||||
fn ssdp_enabled(&self) -> bool;
|
||||
|
||||
/// Crée et initialise un serveur UPnP complet (factory method)
|
||||
///
|
||||
/// Cette méthode factory initialise l'infrastructure UPnP complète :
|
||||
/// - Serveur HTTP (via pmoserver)
|
||||
/// - Caches (couvertures + audio)
|
||||
/// - Logging
|
||||
/// - Serveur SSDP
|
||||
///
|
||||
/// Après cette méthode, l'utilisateur doit :
|
||||
/// - Enregistrer ses devices via `register_device()`
|
||||
/// - Enregistrer ses sources musicales
|
||||
/// - Appeler `wait()` pour attendre l'arrêt
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Un serveur UPnP prêt à l'emploi
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Retourne une erreur si l'initialisation échoue (config, caches, SSDP, etc.)
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// use pmoupnp::UpnpServerExt;
|
||||
/// use pmoserver::Server;
|
||||
///
|
||||
/// let mut server = Server::create_upnp_server().await?;
|
||||
/// server.register_device(my_device).await?;
|
||||
/// server.wait().await;
|
||||
/// ```
|
||||
async fn create_upnp_server() -> Result<Server, anyhow::Error>;
|
||||
}
|
||||
|
||||
// Implémentation du trait UpnpServer pour pmoserver::Server
|
||||
impl UpnpServerExt for Server {
|
||||
async fn register_device(&mut self, device: Arc<Device>) -> Result<Arc<DeviceInstance>, DeviceError> {
|
||||
use tracing::info;
|
||||
|
||||
// Créer l'instance (retourne déjà un Arc<DeviceInstance>)
|
||||
let di = device.create_instance();
|
||||
|
||||
@@ -164,6 +233,16 @@ impl UpnpServerExt for Server {
|
||||
.register(di.clone())
|
||||
.map_err(|e| DeviceError::UrlRegistrationError(e))?;
|
||||
|
||||
// Annoncer via SSDP (si initialisé)
|
||||
if self.ssdp_enabled() {
|
||||
let ssdp_opt = SSDP_SERVER.read().unwrap();
|
||||
if let Some(ref ssdp) = *ssdp_opt {
|
||||
let ssdp_device = di.to_ssdp_device("PMOMusic", "1.0");
|
||||
ssdp.add_device(ssdp_device);
|
||||
info!("✅ SSDP announcement for {}", di.udn());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(di)
|
||||
}
|
||||
|
||||
@@ -281,6 +360,76 @@ impl UpnpServerExt for Server {
|
||||
fn audio_cache(&self) -> Option<Arc<AudioCache>> {
|
||||
crate::cache_registry::get_audio_cache()
|
||||
}
|
||||
|
||||
// ========= SSDP Management Implementation =========
|
||||
|
||||
fn init_ssdp(&self) -> Result<(), std::io::Error> {
|
||||
use tracing::info;
|
||||
|
||||
let mut ssdp_opt = SSDP_SERVER.write().unwrap();
|
||||
if ssdp_opt.is_some() {
|
||||
// Déjà initialisé
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut ssdp = SsdpServer::new();
|
||||
ssdp.start()?;
|
||||
*ssdp_opt = Some(ssdp);
|
||||
|
||||
info!("✅ SSDP server initialized");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ssdp_enabled(&self) -> bool {
|
||||
SSDP_SERVER.read().unwrap().is_some()
|
||||
}
|
||||
|
||||
async fn create_upnp_server() -> Result<Server, anyhow::Error> {
|
||||
use pmoserver::ServerBuilder;
|
||||
use tracing::{info, warn};
|
||||
|
||||
// 1. Créer le serveur depuis la config
|
||||
info!("🔧 Creating UPnP server from configuration...");
|
||||
let mut server = ServerBuilder::new_configured().build();
|
||||
|
||||
// 2. Initialiser le logging HTTP (routes de logs + tracing)
|
||||
info!("📝 Initializing logging...");
|
||||
server.init_logging().await;
|
||||
|
||||
// 3. Initialiser les caches
|
||||
info!("💾 Initializing caches...");
|
||||
match server.init_caches().await {
|
||||
Ok(_) => {
|
||||
info!("✅ Caches initialized");
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("❌ Cache initialization failed: {}", e);
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Le serveur HTTP n'est PAS encore démarré
|
||||
// Il sera démarré après l'enregistrement des devices et routes
|
||||
info!("🌐 HTTP server configured at {}", server.info().base_url);
|
||||
|
||||
// 5. Enregistrer l'API d'introspection UPnP
|
||||
info!("📡 Registering UPnP API...");
|
||||
server.register_upnp_api().await;
|
||||
|
||||
// 6. Initialiser SSDP
|
||||
info!("📡 Initializing SSDP discovery...");
|
||||
match server.init_ssdp() {
|
||||
Ok(_) => info!("✅ SSDP server initialized"),
|
||||
Err(e) => {
|
||||
warn!("❌ SSDP initialization failed: {}", e);
|
||||
return Err(e.into());
|
||||
}
|
||||
}
|
||||
|
||||
info!("🎉 UPnP server infrastructure ready");
|
||||
info!("📝 Next: Register devices and music sources");
|
||||
Ok(server)
|
||||
}
|
||||
}
|
||||
|
||||
/// Fonctions helper pour accéder au registre depuis les handlers.
|
||||
|
||||
Reference in New Issue
Block a user