Files
pmomusic/pmowebrenderer/src/handlers.rs
Eric Coissac 0f0a8d1c91 deps: update esbuild to 0.27.4, rollup to 4.60.0, vite to 7.3.1
deps: update esbuild to 0.27.4, rollup to 4.60.0, vite to 7.3.1

- bump esbuild and all platform-specific binaries from 0.25.10 to 0.27.4
- bump rollup and all platform-specific binaries from 4.52.3 to 4.60.0
- bump vite from 7.1.7 to 7.3.1 (esbuild peer dep updated to ^0.27.0)
- bump dompurify from 3.2.7 to 3.3.3

web: improve cover image retry logic
- increase maxRetries default from 3 to 5
- reduce initial retryDelay from 1000ms to 500ms
- implement exponential backoff: delay = retryDelay * 2^(retryCount - 1)
- add detailed logging for retries and final failure

rust: minor cleanup
- remove unused imports (watch, Url, AudioSegment, SyncMarker)
- add tracing debug logs for cache file requests
- handle missing files gracefully with warning + client retry hint
- add #[allow(async_fn_in_trait)] where needed
2026-03-24 10:49:47 +01:00

292 lines
10 KiB
Rust

//! Action handlers SOAP → Pipeline pour le WebRenderer serveur
//!
//! Chaque handler bridge une action UPnP vers une commande `PipelineControl`
//! envoyée au pipeline audio serveur, ou lit l'état partagé pour les requêtes GET.
use std::sync::Arc;
use pmodidl::DIDLLite;
use pmoupnp::actions::{ActionData, ActionError, ActionHandler, get_value};
use pmoupnp::{get, set};
use pmodidl::ToXmlElement;
use crate::messages::PlaybackState;
use crate::pipeline::{PipelineControl, PipelineHandle, upnp_time_to_seconds};
use crate::state::SharedState;
type ActionFuture =
std::pin::Pin<Box<dyn std::future::Future<Output = Result<ActionData, ActionError>> + Send>>;
// ─── AVTransport Handlers ───────────────────────────────────────────────────
pub fn play_handler(pipeline: PipelineHandle, state: SharedState) -> ActionHandler {
Arc::new(move |data: ActionData| -> ActionFuture {
let pipeline = pipeline.clone();
let state = state.clone();
Box::pin(async move {
// Ne pas écrire Playing ici : c'est stream_source qui le fera
// une fois que les premiers bytes FLAC ont été produits.
// Écrire Transitioning pour signaler que la lecture va démarrer.
state.write().playback_state = PlaybackState::Transitioning;
pipeline.send(PipelineControl::Play).await;
Ok(data)
})
})
}
pub fn stop_handler(pipeline: PipelineHandle, state: SharedState) -> ActionHandler {
Arc::new(move |data: ActionData| -> ActionFuture {
let pipeline = pipeline.clone();
let state = state.clone();
Box::pin(async move {
pipeline.send(PipelineControl::Stop).await;
state.write().playback_state = PlaybackState::Stopped;
Ok(data)
})
})
}
pub fn pause_handler(pipeline: PipelineHandle, state: SharedState) -> ActionHandler {
Arc::new(move |data: ActionData| -> ActionFuture {
let pipeline = pipeline.clone();
let state = state.clone();
Box::pin(async move {
pipeline.send(PipelineControl::Pause).await;
state.write().playback_state = PlaybackState::Paused;
Ok(data)
})
})
}
pub fn next_handler(pipeline: PipelineHandle) -> ActionHandler {
Arc::new(move |data: ActionData| -> ActionFuture {
let pipeline = pipeline.clone();
Box::pin(async move {
pipeline.send(PipelineControl::Play).await;
Ok(data)
})
})
}
pub fn previous_handler(pipeline: PipelineHandle) -> ActionHandler {
Arc::new(move |data: ActionData| -> ActionFuture {
let pipeline = pipeline.clone();
Box::pin(async move {
pipeline.send(PipelineControl::Play).await;
Ok(data)
})
})
}
pub fn seek_handler(pipeline: PipelineHandle) -> ActionHandler {
Arc::new(move |data: ActionData| -> ActionFuture {
let pipeline = pipeline.clone();
Box::pin(async move {
let target: String = get!(&data, "Target", String);
let pos_sec = upnp_time_to_seconds(&target);
pipeline.send(PipelineControl::Seek(pos_sec)).await;
Ok(data)
})
})
}
pub fn set_uri_handler(pipeline: PipelineHandle, state: SharedState) -> ActionHandler {
Arc::new(move |data: ActionData| -> ActionFuture {
let pipeline = pipeline.clone();
let state = state.clone();
Box::pin(async move {
let uri: String = get!(&data, "CurrentURI", String);
let metadata: String = get_value::<String>(&data, "CurrentURIMetaData")
.or_else(|_| {
get_value::<DIDLLite>(&data, "CurrentURIMetaData")
.map(|didl| didl.to_xml())
})
.unwrap_or_default();
// Envoyer l'URI au pipeline serveur (remplace l'envoi WebSocket)
pipeline.send(PipelineControl::LoadUri(uri.clone())).await;
{
let mut s = state.write();
s.current_uri = Some(uri);
s.current_metadata = Some(metadata);
s.playback_state = PlaybackState::Transitioning;
}
Ok(data)
})
})
}
pub fn set_next_uri_handler(pipeline: PipelineHandle, state: SharedState) -> ActionHandler {
Arc::new(move |data: ActionData| -> ActionFuture {
let pipeline = pipeline.clone();
let state = state.clone();
Box::pin(async move {
let uri: String = get!(&data, "NextURI", String);
let metadata: String = get_value::<String>(&data, "NextURIMetaData")
.or_else(|_| {
get_value::<DIDLLite>(&data, "NextURIMetaData")
.map(|didl| didl.to_xml())
})
.unwrap_or_default();
pipeline.send(PipelineControl::LoadNextUri(uri.clone())).await;
{
let mut s = state.write();
s.next_uri = Some(uri);
s.next_metadata = Some(metadata);
}
Ok(data)
})
})
}
pub fn get_position_info_handler(state: SharedState) -> ActionHandler {
Arc::new(move |data: ActionData| -> ActionFuture {
let state = state.clone();
Box::pin(async move {
let mut data = data;
let s = state.read();
set!(
&mut data,
"Track",
if s.current_uri.is_some() { 1u32 } else { 0u32 }
);
set!(
&mut data,
"TrackDuration",
s.duration.clone().unwrap_or_else(|| "00:00:00".to_string())
);
set!(
&mut data,
"TrackURI",
s.current_uri.clone().unwrap_or_default()
);
set!(
&mut data,
"TrackMetaData",
s.current_metadata.clone().unwrap_or_default()
);
set!(
&mut data,
"RelTime",
s.position.clone().unwrap_or_else(|| "00:00:00".to_string())
);
set!(
&mut data,
"AbsTime",
s.position.clone().unwrap_or_else(|| "00:00:00".to_string())
);
Ok(data)
})
})
}
pub fn get_transport_info_handler(state: SharedState) -> ActionHandler {
Arc::new(move |data: ActionData| -> ActionFuture {
let state = state.clone();
Box::pin(async move {
let mut data = data;
let s = state.read();
tracing::info!("[WebRenderer] GetTransportInfo: state={:?}", s.playback_state);
let transport_state = match s.playback_state {
PlaybackState::Stopped => "STOPPED",
PlaybackState::Playing => "PLAYING",
PlaybackState::Paused => "PAUSED_PLAYBACK",
PlaybackState::Transitioning => "TRANSITIONING",
};
set!(&mut data, "CurrentTransportState", transport_state.to_string());
set!(&mut data, "CurrentTransportStatus", "OK".to_string());
set!(&mut data, "CurrentSpeed", "1".to_string());
Ok(data)
})
})
}
pub fn get_media_info_handler(state: SharedState) -> ActionHandler {
Arc::new(move |data: ActionData| -> ActionFuture {
let state = state.clone();
Box::pin(async move {
let mut data = data;
let s = state.read();
set!(
&mut data,
"NrTracks",
if s.current_uri.is_some() { 1u32 } else { 0u32 }
);
set!(&mut data, "CurrentURI", s.current_uri.clone().unwrap_or_default());
set!(&mut data, "CurrentURIMetaData", s.current_metadata.clone().unwrap_or_default());
set!(&mut data, "NextURI", s.next_uri.clone().unwrap_or_default());
set!(&mut data, "NextURIMetaData", s.next_metadata.clone().unwrap_or_default());
Ok(data)
})
})
}
// ─── RenderingControl Handlers ──────────────────────────────────────────────
pub fn set_volume_handler(_pipeline: PipelineHandle, state: SharedState) -> ActionHandler {
Arc::new(move |data: ActionData| -> ActionFuture {
let state = state.clone();
Box::pin(async move {
let volume: u16 = get!(&data, "DesiredVolume", u16);
state.write().volume = volume;
Ok(data)
})
})
}
pub fn get_volume_handler(state: SharedState) -> ActionHandler {
Arc::new(move |data: ActionData| -> ActionFuture {
let state = state.clone();
Box::pin(async move {
let mut data = data;
let volume = state.read().volume;
set!(&mut data, "CurrentVolume", volume);
Ok(data)
})
})
}
pub fn set_mute_handler(_pipeline: PipelineHandle, state: SharedState) -> ActionHandler {
Arc::new(move |data: ActionData| -> ActionFuture {
let state = state.clone();
Box::pin(async move {
let mute: bool = get!(&data, "DesiredMute", bool);
state.write().mute = mute;
Ok(data)
})
})
}
pub fn get_mute_handler(state: SharedState) -> ActionHandler {
Arc::new(move |data: ActionData| -> ActionFuture {
let state = state.clone();
Box::pin(async move {
let mut data = data;
let mute = state.read().mute;
set!(&mut data, "CurrentMute", mute);
Ok(data)
})
})
}
// ─── ConnectionManager Handlers ─────────────────────────────────────────────
pub fn get_protocol_info_handler() -> ActionHandler {
Arc::new(move |data: ActionData| -> ActionFuture {
Box::pin(async move {
let mut data = data;
set!(&mut data, "Source", String::new());
// Serveur-side streaming : on produit du FLAC uniquement
set!(
&mut data,
"Sink",
"http-get:*:audio/flac:*,http-get:*:audio/x-flac:*".to_string()
);
Ok(data)
})
})
}