push-yvskvtsqsmxs #97

Merged
eric merged 5 commits from push-yvskvtsqsmxs into main 2026-04-12 21:10:24 +02:00
11 changed files with 152 additions and 263 deletions
Showing only changes of commit 4b793cec59 - Show all commits

View File

@@ -12,13 +12,14 @@ use crate::errors::ControlPointError;
use crate::linkplay_client::extract_linkplay_host;
use crate::model::{PlaybackState, RendererInfo};
use crate::music_renderer::capabilities::{
PlaybackPosition, PlaybackPositionInfo, PlaybackStatus, QueueTransportControl, RendererBackend,
TransportControl, VolumeControl,
HasContinuousStream, PlaybackPosition, PlaybackPositionInfo, PlaybackStatus,
QueueTransportControl, TransportControl, VolumeControl,
};
use crate::music_renderer::musicrenderer::MusicRendererBackend;
use crate::music_renderer::time_utils::{format_hhmmss, ms_to_seconds, parse_hhmmss_strict};
use crate::music_renderer::HasQueue;
use crate::music_renderer::RendererFromMediaRendererInfo;
use crate::queue::{EnqueueMode, HasQueue, MusicQueue, PlaybackItem, QueueBackend, QueueSnapshot};
use crate::queue::{EnqueueMode, MusicQueue, PlaybackItem, QueueBackend, QueueSnapshot};
use crate::DeviceIdentity;
/// Raw response from Arylic MCU+PINFGET command
@@ -300,13 +301,12 @@ impl PlaybackPosition for ArylicTcpRenderer {
}
}
impl RendererBackend for ArylicTcpRenderer {
fn queue(&self) -> &Arc<Mutex<MusicQueue>> {
&self.queue
}
}
impl QueueTransportControl for ArylicTcpRenderer {
fn play_item(&self, item: &PlaybackItem) -> Result<(), ControlPointError> {
self.play_uri(&item.uri, "")
}
fn play_from_queue(&self) -> Result<(), ControlPointError> {
let mut queue = self.queue.lock().unwrap();
@@ -326,10 +326,12 @@ impl QueueTransportControl for ArylicTcpRenderer {
.get_item(current_index)?
.ok_or_else(|| ControlPointError::QueueError("Current item not found".into()))?;
let uri = item.uri.clone();
drop(queue);
self.play_uri(&uri, "")
let is_stream = crate::music_renderer::is_continuous_stream_url(&item.uri);
*self.continuous_stream.lock().unwrap() = is_stream;
self.play_item(&item)
}
fn play_next(&self) -> Result<(), ControlPointError> {
@@ -370,6 +372,12 @@ impl HasQueue for ArylicTcpRenderer {
}
}
impl HasContinuousStream for ArylicTcpRenderer {
fn continuous_stream(&self) -> &Arc<Mutex<bool>> {
&self.continuous_stream
}
}
#[derive(Debug)]
struct ArylicPlaybackInfo {
status_raw: String,

View File

@@ -1,22 +1,19 @@
// pmocontrol/src/capabilities.rs
use std::sync::{Arc, Mutex};
use crate::queue::{HasQueue, MusicQueue};
use crate::queue::{MusicQueue, QueueBackend};
use crate::{errors::ControlPointError, model::PlaybackState, PlaybackItem};
/// Trait for types that have access to a MusicQueue.
pub trait HasQueue {
fn queue(&self) -> &Arc<Mutex<MusicQueue>>;
}
/// Trait for types that track whether they're playing a continuous stream.
pub trait HasContinuousStream {
fn continuous_stream(&self) -> &Arc<Mutex<bool>>;
}
/// Backend-specific operations for renderers.
///
/// This trait provides access to backend-specific resources like the queue.
pub trait RendererBackend {
/// Returns a reference to the queue associated with this backend.
fn queue(&self) -> &Arc<Mutex<MusicQueue>>;
}
/// Queue-aware transport control operations.
///
/// These operations combine queue management with transport control,

View File

@@ -23,13 +23,14 @@ use crate::discovery::chromecast_discovery::{
use crate::errors::ControlPointError;
use crate::model::{PlaybackState, RendererInfo};
use crate::music_renderer::capabilities::{
PlaybackPosition, PlaybackPositionInfo, PlaybackStatus, QueueTransportControl, RendererBackend,
TransportControl, VolumeControl,
HasContinuousStream, PlaybackPosition, PlaybackPositionInfo, PlaybackStatus,
QueueTransportControl, TransportControl, VolumeControl,
};
use crate::music_renderer::musicrenderer::MusicRendererBackend;
use crate::music_renderer::time_utils::{format_hhmmss_f64, parse_hhmmss_strict};
use crate::music_renderer::HasQueue;
use crate::music_renderer::RendererFromMediaRendererInfo;
use crate::queue::{EnqueueMode, HasQueue, MusicQueue, PlaybackItem, QueueBackend, QueueSnapshot};
use crate::queue::{EnqueueMode, MusicQueue, PlaybackItem, QueueBackend, QueueSnapshot};
use crate::DeviceIdentity;
use rust_cast::{
@@ -799,13 +800,12 @@ impl VolumeControl for ChromecastRenderer {
}
}
impl RendererBackend for ChromecastRenderer {
fn queue(&self) -> &Arc<Mutex<MusicQueue>> {
&self.queue
}
}
impl QueueTransportControl for ChromecastRenderer {
fn play_item(&self, item: &PlaybackItem) -> Result<(), ControlPointError> {
self.play_uri(&item.uri, "")
}
fn play_from_queue(&self) -> Result<(), ControlPointError> {
let mut queue = self.queue.lock().unwrap();
@@ -825,10 +825,12 @@ impl QueueTransportControl for ChromecastRenderer {
.get_item(current_index)?
.ok_or_else(|| ControlPointError::QueueError("Current item not found".into()))?;
let uri = item.uri.clone();
drop(queue);
self.play_uri(&uri, "")
let is_stream = crate::music_renderer::is_continuous_stream_url(&item.uri);
*self.continuous_stream.lock().unwrap() = is_stream;
self.play_item(&item)
}
fn play_next(&self) -> Result<(), ControlPointError> {
@@ -868,3 +870,9 @@ impl HasQueue for ChromecastRenderer {
&self.queue
}
}
impl HasContinuousStream for ChromecastRenderer {
fn continuous_stream(&self) -> &Arc<Mutex<bool>> {
&self.continuous_stream
}
}

View File

@@ -10,13 +10,14 @@ use crate::linkplay_client::{
};
use crate::model::{PlaybackState, RendererInfo};
use crate::music_renderer::capabilities::{
PlaybackPosition, PlaybackPositionInfo, PlaybackStatus, QueueTransportControl, RendererBackend,
TransportControl, VolumeControl,
HasContinuousStream, PlaybackPosition, PlaybackPositionInfo, PlaybackStatus,
QueueTransportControl, TransportControl, VolumeControl,
};
use crate::music_renderer::musicrenderer::MusicRendererBackend;
use crate::music_renderer::time_utils::parse_hhmmss_strict;
use crate::music_renderer::HasQueue;
use crate::music_renderer::RendererFromMediaRendererInfo;
use crate::queue::{EnqueueMode, HasQueue, MusicQueue, PlaybackItem, QueueBackend, QueueSnapshot};
use crate::queue::{EnqueueMode, MusicQueue, PlaybackItem, QueueBackend};
use crate::DeviceIdentity;
const DEFAULT_HTTP_TIMEOUT_SECS: u64 = 3;
@@ -178,13 +179,12 @@ impl PlaybackPosition for LinkPlayRenderer {
}
}
impl RendererBackend for LinkPlayRenderer {
fn queue(&self) -> &Arc<Mutex<MusicQueue>> {
&self.queue
}
}
impl QueueTransportControl for LinkPlayRenderer {
fn play_item(&self, item: &PlaybackItem) -> Result<(), ControlPointError> {
self.play_uri(&item.uri, "")
}
fn play_from_queue(&self) -> Result<(), ControlPointError> {
let mut queue = self.queue.lock().unwrap();
@@ -204,10 +204,12 @@ impl QueueTransportControl for LinkPlayRenderer {
.get_item(current_index)?
.ok_or_else(|| ControlPointError::QueueError("Current item not found".into()))?;
let uri = item.uri.clone();
drop(queue);
self.play_uri(&uri, "")
let is_stream = crate::music_renderer::is_continuous_stream_url(&item.uri);
*self.continuous_stream.lock().unwrap() = is_stream;
self.play_item(&item)
}
fn play_next(&self) -> Result<(), ControlPointError> {
@@ -247,3 +249,9 @@ impl HasQueue for LinkPlayRenderer {
&self.queue
}
}
impl HasContinuousStream for LinkPlayRenderer {
fn continuous_stream(&self) -> &Arc<Mutex<bool>> {
&self.continuous_stream
}
}

View File

@@ -6,7 +6,7 @@ mod upnp_renderer;
mod openhome;
mod openhome_renderer;
mod capabilities;
pub mod capabilities;
mod chromecast_renderer;
mod musicrenderer;
@@ -18,13 +18,14 @@ pub mod watcher;
use std::sync::{Arc, Mutex};
pub use crate::music_renderer::capabilities::{
PlaybackPosition, PlaybackPositionInfo, PlaybackStatus,
HasContinuousStream, HasQueue, PlaybackPosition, PlaybackPositionInfo, PlaybackStatus,
QueueTransportControl, TransportControl, VolumeControl,
};
pub use crate::music_renderer::musicrenderer::{MusicRenderer, PlaylistBinding};
pub use crate::music_renderer::sleep_timer::SleepTimer;
pub use crate::music_renderer::stream_detection::is_continuous_stream_url;
use crate::{
RendererInfo, errors::ControlPointError, music_renderer::musicrenderer::MusicRendererBackend,
errors::ControlPointError, music_renderer::musicrenderer::MusicRendererBackend, RendererInfo,
};
pub trait RendererFromMediaRendererInfo {

View File

@@ -19,10 +19,6 @@ use crate::events::RendererEventBus;
use crate::model::RendererEvent;
use crate::model::{PlaybackSource, PlaybackState, RendererInfo, RendererProtocol, TrackMetadata};
use crate::music_renderer::arylic_tcp::ArylicTcpRenderer;
use crate::music_renderer::capabilities::{
PlaybackPosition, PlaybackPositionInfo, PlaybackStatus, QueueTransportControl, RendererBackend,
TransportControl, VolumeControl,
};
use crate::music_renderer::chromecast_renderer::ChromecastRenderer;
use crate::music_renderer::linkplay_renderer::LinkPlayRenderer;
use crate::music_renderer::openhome_renderer::OpenHomeRenderer;
@@ -33,6 +29,10 @@ use crate::music_renderer::watcher::{
WatchedState,
};
use crate::music_renderer::RendererFromMediaRendererInfo;
use crate::music_renderer::{
HasContinuousStream, HasQueue, PlaybackPosition, PlaybackPositionInfo, PlaybackStatus,
QueueTransportControl, TransportControl, VolumeControl,
};
use crate::online::DeviceConnectionState;
use crate::queue::{EnqueueMode, MusicQueue, PlaybackItem, QueueBackend, QueueSnapshot};
use crate::{DeviceId, DeviceIdentity, DeviceOnline};
@@ -998,7 +998,7 @@ impl MusicRenderer {
/// Get a clone of the queue Arc (for async sync operations).
pub fn queue(&self) -> Arc<Mutex<MusicQueue>> {
let backend = self.lock_backend_for("queue");
crate::music_renderer::capabilities::RendererBackend::queue(&*backend).clone()
crate::music_renderer::capabilities::HasQueue::queue(&*backend).clone()
}
/// Get the current queue item without advancing.
@@ -2274,7 +2274,7 @@ impl PlaybackPosition for MusicRendererBackend {
}
}
impl RendererBackend for MusicRendererBackend {
impl HasQueue for MusicRendererBackend {
fn queue(&self) -> &Arc<Mutex<MusicQueue>> {
match self {
MusicRendererBackend::Upnp(r) => r.queue(),
@@ -2287,7 +2287,31 @@ impl RendererBackend for MusicRendererBackend {
}
}
impl HasContinuousStream for MusicRendererBackend {
fn continuous_stream(&self) -> &Arc<Mutex<bool>> {
match self {
MusicRendererBackend::Upnp(r) => r.continuous_stream(),
MusicRendererBackend::OpenHome(r) => r.continuous_stream(),
MusicRendererBackend::LinkPlay(r) => r.continuous_stream(),
MusicRendererBackend::ArylicTcp(r) => r.continuous_stream(),
MusicRendererBackend::Chromecast(cc) => cc.continuous_stream(),
MusicRendererBackend::HybridUpnpArylic { arylic, .. } => arylic.continuous_stream(),
}
}
}
impl QueueTransportControl for MusicRendererBackend {
fn play_item(&self, item: &PlaybackItem) -> Result<(), ControlPointError> {
match self {
MusicRendererBackend::Upnp(r) => r.play_item(item),
MusicRendererBackend::OpenHome(r) => r.play_item(item),
MusicRendererBackend::LinkPlay(r) => r.play_item(item),
MusicRendererBackend::ArylicTcp(r) => r.play_item(item),
MusicRendererBackend::Chromecast(cc) => cc.play_item(item),
MusicRendererBackend::HybridUpnpArylic { upnp, .. } => upnp.play_item(item),
}
}
fn play_from_queue(&self) -> Result<(), ControlPointError> {
match self {
MusicRendererBackend::Upnp(r) => r.play_from_queue(),
@@ -2332,165 +2356,3 @@ impl QueueTransportControl for MusicRendererBackend {
}
}
}
impl QueueBackend for MusicRendererBackend {
fn len(&self) -> Result<usize, ControlPointError> {
match self {
MusicRendererBackend::Upnp(r) => r.len(),
MusicRendererBackend::OpenHome(r) => r.len(),
MusicRendererBackend::LinkPlay(r) => r.len(),
MusicRendererBackend::ArylicTcp(r) => r.len(),
MusicRendererBackend::Chromecast(cc) => cc.len(),
MusicRendererBackend::HybridUpnpArylic { upnp, .. } => upnp.len(),
}
}
fn track_ids(&self) -> Result<Vec<u32>, ControlPointError> {
match self {
MusicRendererBackend::Upnp(r) => r.track_ids(),
MusicRendererBackend::OpenHome(r) => r.track_ids(),
MusicRendererBackend::LinkPlay(r) => r.track_ids(),
MusicRendererBackend::ArylicTcp(r) => r.track_ids(),
MusicRendererBackend::Chromecast(cc) => cc.track_ids(),
MusicRendererBackend::HybridUpnpArylic { upnp, .. } => upnp.track_ids(),
}
}
fn id_to_position(&self, id: u32) -> Result<usize, ControlPointError> {
match self {
MusicRendererBackend::Upnp(r) => r.id_to_position(id),
MusicRendererBackend::OpenHome(r) => r.id_to_position(id),
MusicRendererBackend::LinkPlay(r) => r.id_to_position(id),
MusicRendererBackend::ArylicTcp(r) => r.id_to_position(id),
MusicRendererBackend::Chromecast(cc) => cc.id_to_position(id),
MusicRendererBackend::HybridUpnpArylic { upnp, .. } => upnp.id_to_position(id),
}
}
fn position_to_id(&self, id: usize) -> Result<u32, ControlPointError> {
match self {
MusicRendererBackend::Upnp(r) => r.position_to_id(id),
MusicRendererBackend::OpenHome(r) => r.position_to_id(id),
MusicRendererBackend::LinkPlay(r) => r.position_to_id(id),
MusicRendererBackend::ArylicTcp(r) => r.position_to_id(id),
MusicRendererBackend::Chromecast(cc) => cc.position_to_id(id),
MusicRendererBackend::HybridUpnpArylic { upnp, .. } => upnp.position_to_id(id),
}
}
fn current_track(&self) -> Result<Option<u32>, ControlPointError> {
match self {
MusicRendererBackend::Upnp(r) => r.current_track(),
MusicRendererBackend::OpenHome(r) => r.current_track(),
MusicRendererBackend::LinkPlay(r) => r.current_track(),
MusicRendererBackend::ArylicTcp(r) => r.current_track(),
MusicRendererBackend::Chromecast(cc) => cc.current_track(),
MusicRendererBackend::HybridUpnpArylic { upnp, .. } => upnp.current_track(),
}
}
fn current_index(&self) -> Result<Option<usize>, ControlPointError> {
match self {
MusicRendererBackend::Upnp(r) => r.current_index(),
MusicRendererBackend::OpenHome(r) => r.current_index(),
MusicRendererBackend::LinkPlay(r) => r.current_index(),
MusicRendererBackend::ArylicTcp(r) => r.current_index(),
MusicRendererBackend::Chromecast(cc) => cc.current_index(),
MusicRendererBackend::HybridUpnpArylic { upnp, .. } => upnp.current_index(),
}
}
fn queue_snapshot(&self) -> Result<QueueSnapshot, ControlPointError> {
match self {
MusicRendererBackend::Upnp(r) => r.queue_snapshot(),
MusicRendererBackend::OpenHome(r) => r.queue_snapshot(),
MusicRendererBackend::LinkPlay(r) => r.queue_snapshot(),
MusicRendererBackend::ArylicTcp(r) => r.queue_snapshot(),
MusicRendererBackend::Chromecast(cc) => cc.queue_snapshot(),
MusicRendererBackend::HybridUpnpArylic { upnp, .. } => upnp.queue_snapshot(),
}
}
fn set_index(&mut self, index: Option<usize>) -> Result<(), ControlPointError> {
match self {
MusicRendererBackend::Upnp(r) => r.set_index(index),
MusicRendererBackend::OpenHome(r) => r.set_index(index),
MusicRendererBackend::LinkPlay(r) => r.set_index(index),
MusicRendererBackend::ArylicTcp(r) => r.set_index(index),
MusicRendererBackend::Chromecast(cc) => cc.set_index(index),
MusicRendererBackend::HybridUpnpArylic { upnp, .. } => upnp.set_index(index),
}
}
fn replace_queue(
&mut self,
items: Vec<PlaybackItem>,
current_index: Option<usize>,
) -> Result<(), ControlPointError> {
match self {
MusicRendererBackend::Upnp(r) => r.replace_queue(items, current_index),
MusicRendererBackend::OpenHome(r) => r.replace_queue(items, current_index),
MusicRendererBackend::LinkPlay(r) => r.replace_queue(items, current_index),
MusicRendererBackend::ArylicTcp(r) => r.replace_queue(items, current_index),
MusicRendererBackend::Chromecast(cc) => cc.replace_queue(items, current_index),
MusicRendererBackend::HybridUpnpArylic { upnp, .. } => {
upnp.replace_queue(items, current_index)
}
}
}
fn sync_queue(
&mut self,
items: Vec<PlaybackItem>,
cancel_token: &Arc<AtomicBool>,
on_ready: Option<Box<dyn FnOnce() + Send>>,
) -> Result<(), ControlPointError> {
match self {
MusicRendererBackend::Upnp(r) => r.sync_queue(items, cancel_token, on_ready),
MusicRendererBackend::OpenHome(r) => r.sync_queue(items, cancel_token, on_ready),
MusicRendererBackend::LinkPlay(r) => r.sync_queue(items, cancel_token, on_ready),
MusicRendererBackend::ArylicTcp(r) => r.sync_queue(items, cancel_token, on_ready),
MusicRendererBackend::Chromecast(cc) => cc.sync_queue(items, cancel_token, on_ready),
MusicRendererBackend::HybridUpnpArylic { upnp, .. } => {
upnp.sync_queue(items, cancel_token, on_ready)
}
}
}
fn get_item(&self, index: usize) -> Result<Option<PlaybackItem>, ControlPointError> {
match self {
MusicRendererBackend::Upnp(r) => r.get_item(index),
MusicRendererBackend::OpenHome(r) => r.get_item(index),
MusicRendererBackend::LinkPlay(r) => r.get_item(index),
MusicRendererBackend::ArylicTcp(r) => r.get_item(index),
MusicRendererBackend::Chromecast(cc) => cc.get_item(index),
MusicRendererBackend::HybridUpnpArylic { upnp, .. } => upnp.get_item(index),
}
}
fn replace_item(&mut self, index: usize, item: PlaybackItem) -> Result<(), ControlPointError> {
match self {
MusicRendererBackend::Upnp(r) => r.replace_item(index, item),
MusicRendererBackend::OpenHome(r) => r.replace_item(index, item),
MusicRendererBackend::LinkPlay(r) => r.replace_item(index, item),
MusicRendererBackend::ArylicTcp(r) => r.replace_item(index, item),
MusicRendererBackend::Chromecast(cc) => cc.replace_item(index, item),
MusicRendererBackend::HybridUpnpArylic { upnp, .. } => upnp.replace_item(index, item),
}
}
fn enqueue_items(
&mut self,
items: Vec<PlaybackItem>,
mode: EnqueueMode,
) -> Result<(), ControlPointError> {
match self {
MusicRendererBackend::Upnp(r) => r.enqueue_items(items, mode),
MusicRendererBackend::OpenHome(r) => r.enqueue_items(items, mode),
MusicRendererBackend::LinkPlay(r) => r.enqueue_items(items, mode),
MusicRendererBackend::ArylicTcp(r) => r.enqueue_items(items, mode),
MusicRendererBackend::Chromecast(cc) => cc.enqueue_items(items, mode),
MusicRendererBackend::HybridUpnpArylic { upnp, .. } => upnp.enqueue_items(items, mode),
}
}
}

View File

@@ -2,8 +2,8 @@ use std::sync::{atomic::AtomicBool, Arc, Mutex};
use std::time::SystemTime;
use crate::music_renderer::capabilities::{
PlaybackPosition, PlaybackPositionInfo, PlaybackStatus, QueueTransportControl, RendererBackend,
TransportControl, VolumeControl,
HasContinuousStream, PlaybackPosition, PlaybackPositionInfo, PlaybackStatus,
QueueTransportControl, TransportControl, VolumeControl,
};
use crate::music_renderer::time_utils::{format_hhmmss_u32, parse_time_flexible};
use crate::DeviceIdentity;
@@ -15,8 +15,9 @@ use crate::music_renderer::openhome::{
build_info_client, build_playlist_client, build_product_client, build_radio_client,
build_time_client, build_volume_client,
};
use crate::music_renderer::HasQueue;
use crate::music_renderer::RendererFromMediaRendererInfo;
use crate::queue::{EnqueueMode, HasQueue, MusicQueue, PlaybackItem, QueueBackend, QueueSnapshot};
use crate::queue::{EnqueueMode, MusicQueue, PlaybackItem, QueueBackend, QueueSnapshot};
use crate::upnp_clients::{
OhInfoClient, OhPlaylistClient, OhProductClient, OhRadioClient, OhTimeClient, OhVolumeClient,
OPENHOME_PLAYLIST_HEAD_ID,
@@ -266,11 +267,6 @@ impl RendererFromMediaRendererInfo for OpenHomeRenderer {
}
}
impl RendererBackend for OpenHomeRenderer {
fn queue(&self) -> &Arc<Mutex<MusicQueue>> {
&self.queue
}
}
impl TransportControl for OpenHomeRenderer {
fn play_uri(&self, uri: &str, meta: &str) -> Result<(), ControlPointError> {
@@ -529,6 +525,11 @@ pub(crate) fn map_openhome_state(raw: &str) -> PlaybackState {
}
impl QueueTransportControl for OpenHomeRenderer {
fn play_item(&self, _item: &PlaybackItem) -> Result<(), ControlPointError> {
let playlist = self.playlist_client_for("play_item")?;
playlist.play()
}
fn play_from_queue(&self) -> Result<(), ControlPointError> {
{
let queue = self
@@ -632,6 +633,12 @@ impl HasQueue for OpenHomeRenderer {
}
}
impl HasContinuousStream for OpenHomeRenderer {
fn continuous_stream(&self) -> &Arc<Mutex<bool>> {
&self.continuous_stream
}
}
impl OpenHomeRenderer {
pub fn replace_queue_with_background(
&mut self,

View File

@@ -185,7 +185,11 @@ fn check_stream_headers(url: &str) -> Result<bool, String> {
trace!(
"Stream detection for {}: content-length={}, chunked={}, streaming_mime={}, is_stream={}",
url, has_content_length, is_chunked, is_streaming_mime, is_stream
url,
has_content_length,
is_chunked,
is_streaming_mime,
is_stream
);
Ok(is_stream)

View File

@@ -4,11 +4,12 @@ use crate::errors::ControlPointError;
use crate::model::PlaybackState;
use crate::music_renderer::capabilities::{
HasContinuousStream, PlaybackPosition, PlaybackPositionInfo, PlaybackStatus,
QueueTransportControl, RendererBackend, TransportControl, VolumeControl,
QueueTransportControl, TransportControl, VolumeControl,
};
use crate::music_renderer::musicrenderer::{build_didl_lite_metadata, MusicRendererBackend};
use crate::music_renderer::HasQueue;
use crate::music_renderer::RendererFromMediaRendererInfo;
use crate::queue::{EnqueueMode, HasQueue, MusicQueue, PlaybackItem, QueueBackend, QueueSnapshot};
use crate::queue::{EnqueueMode, MusicQueue, PlaybackItem, QueueBackend, QueueSnapshot};
use crate::upnp_clients::{
AvTransportClient, ConnectionInfo, ConnectionManagerClient, PositionInfo, ProtocolInfo,
RenderingControlClient,
@@ -174,17 +175,37 @@ impl RendererFromMediaRendererInfo for UpnpRenderer {
}
}
impl RendererBackend for UpnpRenderer {
fn queue(&self) -> &Arc<Mutex<MusicQueue>> {
&self.queue
}
}
impl QueueTransportControl for UpnpRenderer {
fn play_item(&self, item: &PlaybackItem) -> Result<(), ControlPointError> {
let metadata = if let Some(ref track_metadata) = item.metadata {
build_didl_lite_metadata(track_metadata, &item.uri, &item.protocol_info)
} else {
format!(
r#"<DIDL-Lite xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"><item id="0" parentID="-1" restricted="1"><res protocolInfo="{}">{}</res></item></DIDL-Lite>"#,
item.protocol_info, item.uri
)
};
let duration = parse_didl_duration(&metadata);
if let Some(ref dur) = duration {
tracing::debug!("Caching duration from queue DIDL: {}", dur);
*self.cached_duration.lock().unwrap() = Some(dur.clone());
} else {
tracing::debug!("No duration to cache from queue DIDL");
*self.cached_duration.lock().unwrap() = None;
}
let avt = self.avtransport()?;
avt.set_av_transport_uri(&item.uri, &metadata)?;
avt.play(0, "1")?;
Ok(())
}
fn play_from_queue(&self) -> Result<(), ControlPointError> {
let mut queue = self.queue.lock().unwrap();
// Get or initialize current index
let current_index = match queue.current_index()? {
Some(idx) => idx,
None => {
@@ -197,29 +218,15 @@ impl QueueTransportControl for UpnpRenderer {
}
};
// Get the item
let item = queue
.get_item(current_index)?
.ok_or_else(|| ControlPointError::QueueError("Current item not found".into()))?;
drop(queue);
// Build metadata - handle optional TrackMetadata
let metadata = if let Some(ref track_metadata) = item.metadata {
build_didl_lite_metadata(track_metadata, &item.uri, &item.protocol_info)
} else {
// Fallback to minimal DIDL-Lite if no metadata
format!(
r#"<DIDL-Lite xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"><item id="0" parentID="-1" restricted="1"><res protocolInfo="{}">{}</res></item></DIDL-Lite>"#,
item.protocol_info, item.uri
)
};
// Détecte si l'URL est un flux continu en interrogeant le serveur HTTP
let is_stream = crate::music_renderer::is_continuous_stream_url(&item.uri);
*self.continuous_stream.lock().unwrap() = is_stream;
// Log current queue state for debugging
let queue_state = {
let queue = self.queue.lock().unwrap();
let idx = queue.current_index().unwrap_or(None);
@@ -237,22 +244,7 @@ impl QueueTransportControl for UpnpRenderer {
is_stream
);
// Parse et cache la durée du DIDL (fallback pour certains amplis)
let duration = parse_didl_duration(&metadata);
if let Some(ref dur) = duration {
tracing::debug!("Caching duration from queue DIDL: {}", dur);
*self.cached_duration.lock().unwrap() = Some(dur.clone());
} else {
tracing::debug!("No duration to cache from queue DIDL");
*self.cached_duration.lock().unwrap() = None;
}
// UPNP: SetAVTransportURI + Play
let avt = self.avtransport()?;
avt.set_av_transport_uri(&item.uri, &metadata)?;
avt.play(0, "1")?;
Ok(())
self.play_item(&item)
}
fn play_next(&self) -> Result<(), ControlPointError> {
@@ -313,6 +305,12 @@ impl HasQueue for UpnpRenderer {
}
}
impl HasContinuousStream for UpnpRenderer {
fn continuous_stream(&self) -> &Arc<Mutex<bool>> {
&self.continuous_stream
}
}
/// Parse le DIDL-Lite pour extraire la durée du premier élément <res>
fn parse_didl_duration(didl: &str) -> Option<String> {
// Recherche de l'élément <res> (avec ou sans espace après)

View File

@@ -28,15 +28,11 @@
//! - This identity is used by the sync helpers to preserve the current
//! track across queue rebuilds when the MediaServer content changes.
use crate::music_renderer::HasQueue;
use crate::queue::MusicQueue;
use crate::{errors::ControlPointError, PlaybackItem, QueueSnapshot};
use std::sync::{atomic::AtomicBool, Arc, Mutex};
/// Trait for types that have aMusicQueue.
pub trait HasQueue {
fn queue(&self) -> &Arc<Mutex<MusicQueue>>;
}
/// Blanket implementation of QueueBackend for types that have a queue.
/// All methods simply delegate to the underlying MusicQueue.
impl<T: HasQueue> QueueBackend for T {

View File

@@ -6,7 +6,7 @@ mod snapshot;
use std::sync::{Arc, Mutex};
pub use backend::{EnqueueMode, HasQueue, QueueBackend};
pub use backend::{EnqueueMode, QueueBackend};
pub use music_queue::{MusicQueue, SyncScheduleOutcome};
pub use snapshot::{PlaybackItem, QueueSnapshot};