refactor(handlers): clean up UPnP action handlers and pipeline command logic
- Add documentation comments to all AVTransport, RenderingControl & ConnectionManager handlers - Simplify play_handler: remove redundant state writes and clarify flow with comments - Deduplicate pipeline command logic in registry.rs by introducing send_pipeline_command helper - Replace direct PlayerCommand usage with unified PipelineControl enum in registry and handlers - Fix handler signatures: clone pipeline once before Arc closure (next/previous)—avoid redundant clones - Add helper macros for UPnP service factory to reduce boilerplate (add_action, add_var) - Minor formatting fixes: sort imports and align handler assignments
This commit is contained in:
@@ -19,41 +19,32 @@ type ActionFuture =
|
||||
|
||||
// ─── AVTransport Handlers ───────────────────────────────────────────────────
|
||||
|
||||
/// Handler pour l'action UPnP "Play" - lance la lecture du flux audio
|
||||
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 {
|
||||
tracing::info!("[WebRenderer] UPnP Play action invoked");
|
||||
// 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.
|
||||
|
||||
// Check if URI is loaded FIRST, then write state
|
||||
let has_uri = state.read().current_uri.is_some();
|
||||
|
||||
// Single write to update playback_state - avoid holding read lock
|
||||
{
|
||||
let mut s = state.write();
|
||||
s.playback_state = PlaybackState::Transitioning;
|
||||
}
|
||||
|
||||
// Tell frontend to start streaming - include the stream URL
|
||||
if has_uri {
|
||||
// Use a single write to set player_command
|
||||
state.write().player_command = Some(serde_json::json!({
|
||||
"type": "stream",
|
||||
"url": "/api/webrenderer/stream" // Frontend will prefix with instance ID
|
||||
"url": "/api/webrenderer/stream"
|
||||
}));
|
||||
tracing::info!("UPnP Play: stored stream command for frontend polling");
|
||||
}
|
||||
|
||||
pipeline.send(PipelineControl::Play).await;
|
||||
Ok(data)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Handler pour l'action UPnP "Stop" - arrête la lecture
|
||||
pub fn stop_handler(pipeline: PipelineHandle, state: SharedState) -> ActionHandler {
|
||||
Arc::new(move |data: ActionData| -> ActionFuture {
|
||||
let pipeline = pipeline.clone();
|
||||
@@ -66,6 +57,7 @@ pub fn stop_handler(pipeline: PipelineHandle, state: SharedState) -> ActionHandl
|
||||
})
|
||||
}
|
||||
|
||||
/// Handler pour l'action UPnP "Pause" - met en pause la lecture
|
||||
pub fn pause_handler(pipeline: PipelineHandle, state: SharedState) -> ActionHandler {
|
||||
Arc::new(move |data: ActionData| -> ActionFuture {
|
||||
let pipeline = pipeline.clone();
|
||||
@@ -78,7 +70,9 @@ pub fn pause_handler(pipeline: PipelineHandle, state: SharedState) -> ActionHand
|
||||
})
|
||||
}
|
||||
|
||||
/// Handler pour l'action UPnP "Next" - passe à la piste suivante
|
||||
pub fn next_handler(pipeline: PipelineHandle) -> ActionHandler {
|
||||
let pipeline = pipeline.clone();
|
||||
Arc::new(move |data: ActionData| -> ActionFuture {
|
||||
let pipeline = pipeline.clone();
|
||||
Box::pin(async move {
|
||||
@@ -88,7 +82,9 @@ pub fn next_handler(pipeline: PipelineHandle) -> ActionHandler {
|
||||
})
|
||||
}
|
||||
|
||||
/// Handler pour l'action UPnP "Previous" - retourne au début de la piste actuelle
|
||||
pub fn previous_handler(pipeline: PipelineHandle) -> ActionHandler {
|
||||
let pipeline = pipeline.clone();
|
||||
Arc::new(move |data: ActionData| -> ActionFuture {
|
||||
let pipeline = pipeline.clone();
|
||||
Box::pin(async move {
|
||||
@@ -98,6 +94,7 @@ pub fn previous_handler(pipeline: PipelineHandle) -> ActionHandler {
|
||||
})
|
||||
}
|
||||
|
||||
/// Handler pour l'action UPnP "Seek" - seek à une position donnée
|
||||
pub fn seek_handler(pipeline: PipelineHandle) -> ActionHandler {
|
||||
Arc::new(move |data: ActionData| -> ActionFuture {
|
||||
let pipeline = pipeline.clone();
|
||||
@@ -110,6 +107,7 @@ pub fn seek_handler(pipeline: PipelineHandle) -> ActionHandler {
|
||||
})
|
||||
}
|
||||
|
||||
/// Handler pour l'action UPnP "SetAVTransportURI" - définit l'URI à jouer
|
||||
pub fn set_uri_handler(pipeline: PipelineHandle, state: SharedState) -> ActionHandler {
|
||||
Arc::new(move |data: ActionData| -> ActionFuture {
|
||||
let pipeline = pipeline.clone();
|
||||
@@ -125,8 +123,6 @@ pub fn set_uri_handler(pipeline: PipelineHandle, state: SharedState) -> ActionHa
|
||||
.unwrap_or_default();
|
||||
|
||||
tracing::info!(uri = %uri, "SetAVTransportURI handler called - loading URI into pipeline");
|
||||
|
||||
// Envoyer l'URI au pipeline serveur (remplace l'envoi WebSocket)
|
||||
pipeline.send(PipelineControl::LoadUri(uri.clone())).await;
|
||||
|
||||
{
|
||||
@@ -140,6 +136,7 @@ pub fn set_uri_handler(pipeline: PipelineHandle, state: SharedState) -> ActionHa
|
||||
})
|
||||
}
|
||||
|
||||
/// Handler pour l'action UPnP "SetNextAVTransportURI" - définit l'URI suivante (gapless)
|
||||
pub fn set_next_uri_handler(pipeline: PipelineHandle, state: SharedState) -> ActionHandler {
|
||||
Arc::new(move |data: ActionData| -> ActionFuture {
|
||||
let pipeline = pipeline.clone();
|
||||
@@ -165,6 +162,7 @@ pub fn set_next_uri_handler(pipeline: PipelineHandle, state: SharedState) -> Act
|
||||
})
|
||||
}
|
||||
|
||||
/// Handler pour l'action UPnP "GetPositionInfo" - retourne la position actuelle
|
||||
pub fn get_position_info_handler(state: SharedState) -> ActionHandler {
|
||||
Arc::new(move |data: ActionData| -> ActionFuture {
|
||||
let state = state.clone();
|
||||
@@ -206,6 +204,7 @@ pub fn get_position_info_handler(state: SharedState) -> ActionHandler {
|
||||
})
|
||||
}
|
||||
|
||||
/// Handler pour l'action UPnP "GetTransportInfo" - retourne l'état du transport
|
||||
pub fn get_transport_info_handler(state: SharedState) -> ActionHandler {
|
||||
Arc::new(move |data: ActionData| -> ActionFuture {
|
||||
let state = state.clone();
|
||||
@@ -227,6 +226,7 @@ pub fn get_transport_info_handler(state: SharedState) -> ActionHandler {
|
||||
})
|
||||
}
|
||||
|
||||
/// Handler pour l'action UPnP "GetMediaInfo" - retourne les infos du média
|
||||
pub fn get_media_info_handler(state: SharedState) -> ActionHandler {
|
||||
Arc::new(move |data: ActionData| -> ActionFuture {
|
||||
let state = state.clone();
|
||||
@@ -249,6 +249,7 @@ pub fn get_media_info_handler(state: SharedState) -> ActionHandler {
|
||||
|
||||
// ─── RenderingControl Handlers ──────────────────────────────────────────────
|
||||
|
||||
/// Handler pour l'action UPnP "SetVolume" - définit le volume
|
||||
pub fn set_volume_handler(_pipeline: PipelineHandle, state: SharedState) -> ActionHandler {
|
||||
Arc::new(move |data: ActionData| -> ActionFuture {
|
||||
let state = state.clone();
|
||||
@@ -260,6 +261,7 @@ pub fn set_volume_handler(_pipeline: PipelineHandle, state: SharedState) -> Acti
|
||||
})
|
||||
}
|
||||
|
||||
/// Handler pour l'action UPnP "GetVolume" - retourne le volume actuel
|
||||
pub fn get_volume_handler(state: SharedState) -> ActionHandler {
|
||||
Arc::new(move |data: ActionData| -> ActionFuture {
|
||||
let state = state.clone();
|
||||
@@ -272,6 +274,7 @@ pub fn get_volume_handler(state: SharedState) -> ActionHandler {
|
||||
})
|
||||
}
|
||||
|
||||
/// Handler pour l'action UPnP "SetMute" - définit le mute
|
||||
pub fn set_mute_handler(_pipeline: PipelineHandle, state: SharedState) -> ActionHandler {
|
||||
Arc::new(move |data: ActionData| -> ActionFuture {
|
||||
let state = state.clone();
|
||||
@@ -283,6 +286,7 @@ pub fn set_mute_handler(_pipeline: PipelineHandle, state: SharedState) -> Action
|
||||
})
|
||||
}
|
||||
|
||||
/// Handler pour l'action UPnP "GetMute" - retourne l'état mute
|
||||
pub fn get_mute_handler(state: SharedState) -> ActionHandler {
|
||||
Arc::new(move |data: ActionData| -> ActionFuture {
|
||||
let state = state.clone();
|
||||
@@ -297,12 +301,12 @@ pub fn get_mute_handler(state: SharedState) -> ActionHandler {
|
||||
|
||||
// ─── ConnectionManager Handlers ─────────────────────────────────────────────
|
||||
|
||||
/// Handler pour l'action UPnP "GetProtocolInfo" - retourne les protocoles supportés
|
||||
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",
|
||||
|
||||
@@ -16,7 +16,7 @@ use std::time::SystemTime;
|
||||
use pmoupnp::devices::DeviceInstance;
|
||||
|
||||
use crate::error::WebRendererError;
|
||||
use crate::pipeline::{InstancePipeline, PipelineHandle};
|
||||
use crate::pipeline::{InstancePipeline, PipelineControl, PipelineHandle};
|
||||
use crate::renderer::WebRendererFactory;
|
||||
use crate::state::{RendererState, SharedState};
|
||||
|
||||
@@ -273,41 +273,36 @@ impl RendererRegistry {
|
||||
}
|
||||
}
|
||||
|
||||
/// Charge une URI dans le pipeline
|
||||
pub async fn load_uri(&self, instance_id: &str, uri: String) {
|
||||
// Get pipeline handle before async call to avoid holding lock across await
|
||||
let pipeline = self.instances.read().get(instance_id).map(|i| i.pipeline.clone());
|
||||
/// Consume and send a command to the pipeline
|
||||
async fn send_pipeline_command(&self, instance_id: &str, cmd: PipelineControl) {
|
||||
let pipeline = self.get_pipeline(instance_id);
|
||||
if let Some(pipeline) = pipeline {
|
||||
use pmoaudio_ext::PlayerCommand;
|
||||
pipeline.send(PlayerCommand::LoadUri(uri.clone())).await;
|
||||
pipeline.send(PlayerCommand::Play).await;
|
||||
tracing::info!(instance_id = %instance_id, uri = %uri, "loaded URI");
|
||||
pipeline.send(cmd).await;
|
||||
} else {
|
||||
tracing::error!(instance_id = %instance_id, "Instance not found for pipeline command");
|
||||
}
|
||||
}
|
||||
|
||||
fn get_pipeline(&self, instance_id: &str) -> Option<PipelineHandle> {
|
||||
self.instances.read().get(instance_id).map(|i| i.pipeline.clone())
|
||||
}
|
||||
|
||||
/// Charge une URI dans le pipeline et lance la lecture
|
||||
pub async fn load_uri(&self, instance_id: &str, uri: String) {
|
||||
self.send_pipeline_command(instance_id, PipelineControl::LoadUri(uri.clone())).await;
|
||||
self.send_pipeline_command(instance_id, PipelineControl::Play).await;
|
||||
tracing::info!(instance_id = %instance_id, uri = %uri, "loaded URI");
|
||||
}
|
||||
|
||||
/// Envoie commande play au pipeline
|
||||
pub async fn send_play_command(&self, instance_id: &str) {
|
||||
tracing::info!(instance_id = %instance_id, "send_play_command called");
|
||||
// Get pipeline handle before async call to avoid holding lock across await
|
||||
let pipeline = self.instances.read().get(instance_id).map(|i| i.pipeline.clone());
|
||||
if let Some(pipeline) = pipeline {
|
||||
tracing::info!(instance_id = %instance_id, "Instance found, sending PlayerCommand::Play");
|
||||
use pmoaudio_ext::PlayerCommand;
|
||||
pipeline.send(PlayerCommand::Play).await;
|
||||
tracing::info!(instance_id = %instance_id, "PlayerCommand::Play sent");
|
||||
} else {
|
||||
tracing::error!(instance_id = %instance_id, "Instance not found in send_play_command!");
|
||||
}
|
||||
self.send_pipeline_command(instance_id, PipelineControl::Play).await;
|
||||
}
|
||||
|
||||
/// Envoie commande pause au pipeline
|
||||
pub async fn send_pause_command(&self, instance_id: &str) {
|
||||
// Get pipeline handle before async call to avoid holding lock across await
|
||||
let pipeline = self.instances.read().get(instance_id).map(|i| i.pipeline.clone());
|
||||
if let Some(pipeline) = pipeline {
|
||||
use pmoaudio_ext::PlayerCommand;
|
||||
pipeline.send(PlayerCommand::Pause).await;
|
||||
}
|
||||
self.send_pipeline_command(instance_id, PipelineControl::Pause).await;
|
||||
}
|
||||
|
||||
/// Check if the instance has a current URI loaded
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
//! Construit des Device/Service models UPnP dynamiques avec des action handlers
|
||||
//! qui relaient les commandes SOAP vers le navigateur via WebSocket.
|
||||
|
||||
use std::sync::Arc;
|
||||
use thiserror::Error;
|
||||
use pmoupnp::actions::{Action, Argument};
|
||||
use pmoupnp::devices::Device;
|
||||
use pmoupnp::services::Service;
|
||||
use std::sync::Arc;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::handlers;
|
||||
use crate::pipeline::PipelineHandle;
|
||||
@@ -46,6 +46,37 @@ pub enum FactoryError {
|
||||
VariableError(String),
|
||||
}
|
||||
|
||||
macro_rules! add_action_arg {
|
||||
($action:ident, $name:expr, $var:ident, $direction:ident) => {{
|
||||
$action
|
||||
.add_argument(Arc::new(Argument::new_$direction(
|
||||
$name.to_string(),
|
||||
Arc::clone(&$var),
|
||||
)))
|
||||
.map_err(|e| FactoryError::ActionError(e.to_string()))
|
||||
}};
|
||||
($action:ident, $name:expr, $var:ident, in) => {
|
||||
add_action_arg!($action, $name, $var, in)
|
||||
};
|
||||
($action:ident, $name:expr, $var:ident, out) => {
|
||||
add_action_arg!($action, $name, $var, out)
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! add_action {
|
||||
($svc:ident, $action:ident) => {{
|
||||
$svc.add_action(Arc::new($action))
|
||||
.map_err(|e| FactoryError::ActionError(e.to_string()))
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! add_var {
|
||||
($svc:ident, $var:ident) => {{
|
||||
$svc.add_variable(Arc::clone(&$var))
|
||||
.map_err(|e| FactoryError::VariableError(e.to_string()))
|
||||
}};
|
||||
}
|
||||
|
||||
/// Extrait un nom de navigateur court depuis un User-Agent complet.
|
||||
fn extract_browser_name(ua: &str) -> &str {
|
||||
if ua.contains("Edg/") || ua.contains("EdgA/") {
|
||||
@@ -261,7 +292,10 @@ impl WebRendererFactory {
|
||||
Arc::clone(&AVTRANSPORTNEXTURIMETADATA),
|
||||
)))
|
||||
.map_err(|e| FactoryError::ActionError(format!("{:?}", e)))?;
|
||||
set_next_uri.set_handler(handlers::set_next_uri_handler(pipeline.clone(), state.clone()));
|
||||
set_next_uri.set_handler(handlers::set_next_uri_handler(
|
||||
pipeline.clone(),
|
||||
state.clone(),
|
||||
));
|
||||
add_action(&mut svc, Arc::new(set_next_uri))?;
|
||||
|
||||
// GetPositionInfo
|
||||
@@ -453,7 +487,10 @@ impl WebRendererFactory {
|
||||
Arc::clone(&VOLUME),
|
||||
)))
|
||||
.map_err(|e| FactoryError::ActionError(format!("{:?}", e)))?;
|
||||
set_vol.set_handler(handlers::set_volume_handler(pipeline.clone(), state.clone()));
|
||||
set_vol.set_handler(handlers::set_volume_handler(
|
||||
pipeline.clone(),
|
||||
state.clone(),
|
||||
));
|
||||
svc.add_action(Arc::new(set_vol))
|
||||
.map_err(|e| FactoryError::ActionError(format!("{:?}", e)))?;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user