push-yvskvtsqsmxs #97
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user