2025-12-06 13:37:42 +01:00
|
|
|
//! Internal (local) queue implementation for PMOControl.
|
|
|
|
|
//!
|
|
|
|
|
//! This module provides a concrete implementation of the generic
|
|
|
|
|
//! `QueueBackend` trait for queues that are fully managed inside the
|
|
|
|
|
//! ControlPoint, without delegating playlist management to a remote
|
|
|
|
|
//! backend (like OpenHome).
|
|
|
|
|
//!
|
|
|
|
|
//! In this design, each queue instance is associated to exactly one
|
|
|
|
|
//! renderer. The queue does not need to know the renderer identifier:
|
|
|
|
|
//! it is "bound" to the renderer by construction, and will be stored
|
|
|
|
|
//! directly in the runtime (inside a higher-level `MusicQueue` enum).
|
|
|
|
|
//!
|
|
|
|
|
//! This internal queue:
|
|
|
|
|
//! - owns its list of `PlaybackItem`s,
|
|
|
|
|
//! - maintains a `current_index`,
|
|
|
|
|
//! - never starts playback (transport control is handled elsewhere).
|
|
|
|
|
|
2025-12-28 17:45:06 +01:00
|
|
|
use std::sync::{Arc, Mutex};
|
|
|
|
|
|
2025-12-06 13:37:42 +01:00
|
|
|
use anyhow::Result;
|
|
|
|
|
|
2025-12-28 17:45:06 +01:00
|
|
|
use crate::{
|
|
|
|
|
DeviceId, RendererInfo,
|
|
|
|
|
DeviceIdentity,
|
|
|
|
|
errors::ControlPointError,
|
|
|
|
|
queue::{
|
|
|
|
|
MusicQueue,
|
|
|
|
|
backend::{PlaybackItem, QueueBackend, QueueSnapshot},
|
|
|
|
|
},
|
|
|
|
|
};
|
2025-12-06 13:37:42 +01:00
|
|
|
|
|
|
|
|
/// Internal/local queue implementation.
|
|
|
|
|
///
|
|
|
|
|
/// This is the simplest possible queue backend:
|
|
|
|
|
/// - a `Vec<PlaybackItem>`
|
|
|
|
|
/// - plus an optional `current_index`.
|
|
|
|
|
///
|
|
|
|
|
/// It does not talk to any remote service. All operations are pure
|
|
|
|
|
/// structural mutations on in-memory data.
|
2025-12-28 17:45:06 +01:00
|
|
|
#[derive(Clone, Debug)]
|
2025-12-06 13:37:42 +01:00
|
|
|
pub struct InternalQueue {
|
2025-12-28 17:45:06 +01:00
|
|
|
renderer_id: DeviceId,
|
2025-12-06 13:37:42 +01:00
|
|
|
items: Vec<PlaybackItem>,
|
|
|
|
|
current_index: Option<usize>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl InternalQueue {
|
|
|
|
|
/// Creates an empty internal queue.
|
2025-12-28 17:45:06 +01:00
|
|
|
pub fn new(renderer_id: DeviceId) -> Self {
|
2025-12-06 13:37:42 +01:00
|
|
|
Self {
|
2025-12-28 17:45:06 +01:00
|
|
|
renderer_id,
|
2025-12-06 13:37:42 +01:00
|
|
|
items: Vec::new(),
|
|
|
|
|
current_index: None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-28 17:45:06 +01:00
|
|
|
pub fn from_renderer_info(
|
|
|
|
|
info: &RendererInfo,
|
|
|
|
|
) -> Result<InternalQueue, ControlPointError> {
|
|
|
|
|
Ok(InternalQueue::new(
|
|
|
|
|
info.id(),
|
|
|
|
|
),
|
|
|
|
|
)
|
2025-12-06 13:37:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Exposes a read-only view of the underlying items.
|
|
|
|
|
pub fn items(&self) -> &[PlaybackItem] {
|
|
|
|
|
&self.items
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Exposes the current index (read-only).
|
|
|
|
|
pub fn current_index(&self) -> Option<usize> {
|
|
|
|
|
self.current_index
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl QueueBackend for InternalQueue {
|
2025-12-28 17:45:06 +01:00
|
|
|
fn queue_snapshot(&self) -> Result<QueueSnapshot, ControlPointError> {
|
|
|
|
|
let mut items = self.items.clone();
|
|
|
|
|
for (i, item) in items.iter_mut().enumerate() {
|
|
|
|
|
item.backend_id = i;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-06 13:37:42 +01:00
|
|
|
Ok(QueueSnapshot {
|
2025-12-28 17:45:06 +01:00
|
|
|
items,
|
2025-12-06 13:37:42 +01:00
|
|
|
current_index: self.current_index,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-28 17:45:06 +01:00
|
|
|
fn set_index(&mut self, index: Option<usize>) -> Result<(), ControlPointError> {
|
2025-12-06 13:37:42 +01:00
|
|
|
match index {
|
|
|
|
|
None => {
|
|
|
|
|
self.current_index = None;
|
|
|
|
|
}
|
|
|
|
|
Some(i) => {
|
|
|
|
|
if i < self.items.len() {
|
|
|
|
|
self.current_index = Some(i);
|
|
|
|
|
} else {
|
|
|
|
|
self.current_index = None;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn replace_queue(
|
|
|
|
|
&mut self,
|
|
|
|
|
items: Vec<PlaybackItem>,
|
|
|
|
|
current_index: Option<usize>,
|
2025-12-28 17:45:06 +01:00
|
|
|
) -> Result<(), ControlPointError> {
|
2025-12-06 13:37:42 +01:00
|
|
|
self.items = items;
|
|
|
|
|
self.current_index = current_index.filter(|&i| i < self.items.len());
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-28 17:45:06 +01:00
|
|
|
fn get_item(&self, index: usize) -> Result<Option<PlaybackItem>, ControlPointError> {
|
2025-12-06 13:37:42 +01:00
|
|
|
Ok(self.items.get(index).cloned())
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-28 17:45:06 +01:00
|
|
|
fn replace_item(&mut self, index: usize, item: PlaybackItem) -> Result<(), ControlPointError> {
|
2025-12-06 13:37:42 +01:00
|
|
|
if index < self.items.len() {
|
|
|
|
|
self.items[index] = item;
|
|
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
}
|