diff --git a/.kilo/plans/1775285337131-neon-mountain.md b/.kilo/plans/1775285337131-neon-mountain.md index d7ccea16..c7a238b8 100644 --- a/.kilo/plans/1775285337131-neon-mountain.md +++ b/.kilo/plans/1775285337131-neon-mountain.md @@ -360,4 +360,46 @@ fn transform_external_cover_url(url: &str) -> String { 1. **Nouveau** : `pmocovers/src/proxy.rs` - Endpoint de proxy 2. **Modifier** : `pmocontrol/src/pmoserver_ext.rs` - Transformer les album_art_uri -3. **Modifier** : `pmocontrol/src/sse.rs` - Transformer les album_art_uri dans les événements \ No newline at end of file +3. **Modifier** : `pmocontrol/src/sse.rs` - Transformer les album_art_uri dans les événements + +--- + +## Plan: Passer le SSE en mode Async + +### Contexte + +Le SSE de PMO Control est **déjà async** (fonctions `pub async fn`), mais le traitement des événements utilise des fonctions **synchrones** (`fn renderer_event_to_payload` → `transform_cover_url_sync`). Cela nécessite des workarounds (threads avec runtime tokio séparés). + +### Problèmes actuels + +1. **Nested runtime**: `std::thread::spawn` avec `tokio::runtime::Runtime::new()` dans chaque appel +2. **Performance dégradée**: Création d'un thread par URL de cover +3. **Code complexe**: Workarounds pour exécuter de l'async dans du sync + +### Solution + +Rendre le traitement des événements **entièrement async** : + +1. **Modifier** `renderer_event_to_payload` → `async fn renderer_event_to_payload` +2. **Modifier** `transform_cover_url_sync` → `transform_cover_url` (async) avec `.await` direct +3. **Supprimer** le workaround `proxy_cover_url_sync` dans `pmocovers` (quand les .await fonctionnent) + +### Avantages attendus + +1. **Fluidité accrue**: Pas de thread par cover,真正的 async/await +2. **Meilleure réactivité**: Pas de blocking sur les événements SSE +3. **Code plus propre**: Plus de workarounds, plus de runtime imbriqué +4. **Meilleure scalabilité**: Plus de création de thread + +### Fichiers à modifier + +1. `pmocontrol/src/sse.rs`: + - `renderer_event_to_payload` → `async fn` + - `media_server_event_to_payload` → `async fn` + - `all_events_sse`: utiliser les versions async + +2. `pmocontrol/src/pmoserver_ext.rs`: + - Utiliser `transform_cover_url` (async) avec `.await` au lieu de `transform_cover_url_sync` + +3. `pmocontrol/src/control_point.rs` (si nécessaire): + - Adapter les appels aux fonctions async \ No newline at end of file diff --git a/pmocontrol/src/sse.rs b/pmocontrol/src/sse.rs index 0e79cf7f..1d29580f 100644 --- a/pmocontrol/src/sse.rs +++ b/pmocontrol/src/sse.rs @@ -248,7 +248,7 @@ pub enum UnifiedEventPayload { /// Cette fonction centralise la conversion pour éviter la duplication de code /// entre les différents streams SSE (renderers-only et all-events). #[cfg(feature = "pmoserver")] -fn renderer_event_to_payload( +async fn renderer_event_to_payload( event: RendererEvent, timestamp: chrono::DateTime, base_url: &pmoserver::BaseUrl, @@ -281,7 +281,7 @@ fn renderer_event_to_payload( title: metadata.title, artist: metadata.artist, album: metadata.album, - album_art_uri: transform_cover_url_sync(metadata.album_art_uri.as_deref(), base_url), + album_art_uri: transform_cover_url(metadata.album_art_uri.as_deref(), base_url).await, timestamp, }, RendererEvent::QueueUpdated { id, queue_length } => RendererEventPayload::QueueUpdated { @@ -361,7 +361,7 @@ fn renderer_event_to_payload( /// Cette fonction centralise la conversion pour éviter la duplication de code /// entre les différents streams SSE (servers-only et all-events). #[cfg(feature = "pmoserver")] -fn media_server_event_to_payload( +async fn media_server_event_to_payload( event: MediaServerEvent, timestamp: chrono::DateTime, ) -> MediaServerEventPayload { @@ -480,7 +480,7 @@ pub async fn renderer_events_sse( // Regular events from the control point Some(event) = rx_tokio.recv() => { let timestamp = chrono::Utc::now(); - let payload = renderer_event_to_payload(event, timestamp, &base_url); + let payload = renderer_event_to_payload(event, timestamp, &base_url).await; if let Ok(json) = serde_json::to_string(&payload) { yield Ok::<_, axum::Error>(Event::default().event("renderer").data(json)); @@ -602,7 +602,7 @@ pub async fn media_server_events_sse( // Regular events from the control point Some(event) = rx_tokio.recv() => { let timestamp = chrono::Utc::now(); - let payload = media_server_event_to_payload(event, timestamp); + let payload = media_server_event_to_payload(event, timestamp).await; if let Ok(json) = serde_json::to_string(&payload) { yield Ok::<_, axum::Error>(Event::default().event("media_server").data(json)); @@ -758,7 +758,7 @@ pub async fn all_events_sse( tokio::select! { Some(event) = renderer_rx_tokio.recv() => { let timestamp = chrono::Utc::now(); - let renderer_payload = renderer_event_to_payload(event, timestamp, &base_url); + let renderer_payload = renderer_event_to_payload(event, timestamp, &base_url).await; let payload = UnifiedEventPayload::Renderer(renderer_payload); @@ -768,7 +768,7 @@ pub async fn all_events_sse( } Some(event) = server_rx_tokio.recv() => { let timestamp = chrono::Utc::now(); - let server_payload = media_server_event_to_payload(event, timestamp); + let server_payload = media_server_event_to_payload(event, timestamp).await; let payload = UnifiedEventPayload::MediaServer(server_payload); if let Ok(json) = serde_json::to_string(&payload) {