⬆️ version bump to v0.3.35 & refactor MediaRenderer stream URL handling

- Bump version to v0.3.35 in Cargo.toml, lockfile and `version.txt`
- Refactor MediaRenderer to accept dynamic stream URL base instead of hardcoded `/api/webrenderer` path
- Remove `PlayerStateReport`, moving it to pmoweb/renderer as local type for better encapsulation
- Update `register_or_reconnect` and device creation to accept renderer name, stream URL base via closure
- Replace `update_player_state` and `get_pending_command`, delegating state/command access directly via instance lookup
- Simplify device creation by using `Device::new_from_config` and removing manual model_name setting
This commit is contained in:
2026-04-05 21:42:38 +02:00
parent e375d447d4
commit e055133e4f
9 changed files with 77 additions and 78 deletions

View File

@@ -18,9 +18,10 @@ pub fn play_handler(
pipeline: PipelineHandle,
state: SharedState,
instance_id: String,
stream_url_base: String,
) -> ActionHandler {
action_handler!(
captures(pipeline, state, instance_id) | data | {
captures(pipeline, state, instance_id, stream_url_base) | data | {
tracing::info!("[MediaRenderer] UPnP Play action invoked");
let has_uri = state.read().current_uri.is_some();
if !has_uri {
@@ -31,7 +32,7 @@ pub fn play_handler(
let mut s = state.write();
s.playback_state = PlaybackState::Transitioning;
s.push_command(crate::adapter::DeviceCommand::Stream {
url: format!("/api/webrenderer/{}/stream", instance_id),
url: format!("{}/{}/stream", stream_url_base, instance_id),
});
}
pipeline.flac_handle.resume();

View File

@@ -26,7 +26,7 @@ pub mod state;
pub use error::MediaRendererError;
pub use handlers::*;
pub use messages::{PlaybackState, PlayerStateReport};
pub use messages::PlaybackState;
pub use pipeline::{PipelineControl, PipelineHandle, seconds_to_upnp_time, upnp_time_to_seconds, InstancePipeline};
pub use registry::{MediaRendererInstance, MediaRendererRegistry};
pub use state::{RendererState, SharedState};

View File

@@ -11,11 +11,3 @@ pub enum PlaybackState {
Transitioning,
}
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
pub struct PlayerStateReport {
pub position_sec: Option<f64>,
pub duration_sec: Option<f64>,
pub state: Option<String>,
pub ready_state: Option<String>,
}

View File

@@ -63,8 +63,9 @@ impl MediaRendererRegistry {
pub async fn register_or_reconnect(
&self,
instance_id: &str,
user_agent: &str,
adapter: Arc<dyn DeviceAdapter>,
stream_url_base: &str,
renderer_name: &str,
adapter_fn: impl FnOnce(SharedState) -> Arc<dyn DeviceAdapter>,
) -> Result<(String, String, bool), MediaRendererError> {
if let Some(cancel) = self.pending_unregister.write().remove(instance_id) {
tracing::info!(instance_id = %instance_id, "MediaRenderer: cancelled pending unregister (page reload)");
@@ -76,8 +77,8 @@ impl MediaRendererRegistry {
if let Some(existing) = instances.get(instance_id) {
tracing::info!(instance_id = %instance_id, "MediaRenderer: reconnecting existing instance");
#[cfg(feature = "pmoserver")]
self.register_with_control_point(&existing.device_instance)?;
let stream_url = format!("/api/webrenderer/{}/stream", instance_id);
self.register_with_control_point(&existing.device_instance, renderer_name)?;
let stream_url = format!("{}/{}/stream", stream_url_base, instance_id);
let should_play = {
let s = existing.state.read();
s.current_uri.is_some() && matches!(
@@ -89,9 +90,9 @@ impl MediaRendererRegistry {
}
}
let instance = self.create_instance_with_adapter(instance_id, user_agent, adapter).await?;
let instance = self.create_instance_with_adapter(instance_id, stream_url_base, renderer_name, adapter_fn).await?;
let instance = Arc::new(instance);
let stream_url = format!("/api/webrenderer/{}/stream", instance_id);
let stream_url = format!("{}/{}/stream", stream_url_base, instance_id);
let udn = instance.udn.clone();
{
@@ -223,47 +224,14 @@ impl MediaRendererRegistry {
});
}
pub async fn update_player_state(
&self,
instance_id: &str,
report: crate::messages::PlayerStateReport,
) {
let instances = self.instances.read();
if let Some(instance) = instances.get(instance_id) {
let mut state = instance.state.write();
if let Some(pos) = report.position_sec {
state.position = Some(crate::pipeline::seconds_to_upnp_time(pos));
}
if let Some(dur) = report.duration_sec {
state.duration = Some(crate::pipeline::seconds_to_upnp_time(dur));
}
if let Some(s) = &report.state {
state.playback_state = match s.as_str() {
"playing" => crate::messages::PlaybackState::Playing,
"paused" => crate::messages::PlaybackState::Paused,
"stopped" => crate::messages::PlaybackState::Stopped,
_ => state.playback_state.clone(),
};
}
tracing::debug!(instance_id = %instance_id, position = ?state.position, "player state updated");
}
}
pub async fn get_pending_command(
&self,
instance_id: &str,
) -> Option<serde_json::Value> {
let state = self.instances.read().get(instance_id).map(|i| i.state.clone())?;
let cmd = state.write().pop_command()?;
serde_json::to_value(cmd).ok()
}
/// Créer une nouvelle instance avec un adapter fourni (permet à l'appelant de créer BrowserAdapter)
/// Créer une nouvelle instance avec un adapter fourni via une factory closure.
/// La closure reçoit le SharedState de l'instance afin que l'adapter partage le même état.
pub async fn create_instance_with_adapter(
&self,
instance_id: &str,
user_agent: &str,
adapter: Arc<dyn DeviceAdapter>,
stream_url_base: &str,
renderer_name: &str,
adapter_fn: impl FnOnce(SharedState) -> Arc<dyn DeviceAdapter>,
) -> Result<MediaRendererInstance, MediaRendererError> {
let candidate_udn = instance_id.to_ascii_lowercase();
let full_udn = format!("uuid:{}", candidate_udn);
@@ -277,6 +245,7 @@ impl MediaRendererRegistry {
}
let state: SharedState = Arc::new(parking_lot::RwLock::new(RendererState::default()));
let adapter = adapter_fn(state.clone());
#[cfg(feature = "pmoserver")]
let (device_instance, pipeline) = {
@@ -306,9 +275,10 @@ impl MediaRendererRegistry {
let device = MediaRendererFactory::create_device_with_pipeline(
instance_id,
"MediaRenderer",
user_agent,
"WebRenderer",
pipeline.clone(),
state.clone(),
stream_url_base,
)
.map_err(|e| MediaRendererError::DeviceCreationError(e.to_string()))?;
@@ -319,7 +289,7 @@ impl MediaRendererRegistry {
.map_err(|e| MediaRendererError::RegistrationError(e.to_string()))?
};
self.register_with_control_point(&di)?;
self.register_with_control_point(&di, renderer_name)?;
(di, ip)
};
@@ -337,9 +307,10 @@ impl MediaRendererRegistry {
let device = MediaRendererFactory::create_device_with_pipeline(
instance_id,
"MediaRenderer",
user_agent,
"WebRenderer",
pipeline.clone(),
state.clone(),
stream_url_base,
)
.map_err(|e| MediaRendererError::DeviceCreationError(e.to_string()))?;
@@ -362,6 +333,7 @@ impl MediaRendererRegistry {
fn register_with_control_point(
&self,
di: &Arc<DeviceInstance>,
renderer_name: &str,
) -> Result<(), MediaRendererError> {
let base_url = di.base_url().to_string();
let udn = di.udn().to_ascii_lowercase();
@@ -397,7 +369,7 @@ impl MediaRendererRegistry {
..Default::default()
},
format!("{}{}", base_url, di.description_route()),
"PMOMusic WebRenderer/2.0".to_string(),
renderer_name.to_string(),
Some("urn:schemas-upnp-org:service:AVTransport:1".to_string()),
avtransport_control_url,
Some("urn:schemas-upnp-org:service:RenderingControl:1".to_string()),

View File

@@ -87,20 +87,20 @@ impl MediaRendererFactory {
pub fn create_device_with_pipeline(
device_name: &str,
device_type: &str,
device_ua: &str,
friendly_name_suffix: &str,
pipeline: PipelineHandle,
state: SharedState,
stream_url_base: &str,
) -> Result<Device, FactoryError> {
let avtransport = Self::build_avtransport(pipeline.clone(), state.clone(), device_name)?;
let avtransport = Self::build_avtransport(pipeline.clone(), state.clone(), device_name, stream_url_base)?;
let renderingcontrol = Self::build_renderingcontrol(state.clone())?;
let connectionmanager = Self::build_connectionmanager()?;
let mut device = Device::new(
let device = Device::new_from_config(
device_name.to_string(),
device_type.to_string(),
device_ua.to_string(),
friendly_name_suffix.to_string(),
);
device.set_model_name("MediaRenderer".to_string());
device
.add_service(Arc::new(avtransport))
.map_err(|e| FactoryError::ServiceError(format!("{:?}", e)))?;
@@ -118,6 +118,7 @@ impl MediaRendererFactory {
pipeline: PipelineHandle,
state: SharedState,
instance_id: &str,
stream_url_base: &str,
) -> Result<Service, FactoryError> {
let mut svc = Service::new("AVTransport".to_string());
@@ -151,6 +152,7 @@ impl MediaRendererFactory {
pipeline.clone(),
state.clone(),
instance_id.to_string(),
stream_url_base.to_string(),
));
add_action(&mut svc, Arc::new(play))?;