Files
pmomusic/pmocontrol/src/model.rs

464 lines
13 KiB
Rust

use crate::music_renderer::{PlaybackPositionInfo, PlaylistBinding};
use crate::{DeviceId, DeviceIdentity};
/// Basic device information for event notifications
/// Contains only the essential identification fields
#[derive(Clone, Debug)]
pub struct DeviceBasicInfo {
pub id: DeviceId,
pub friendly_name: String,
pub model_name: String,
pub manufacturer: String,
}
impl DeviceBasicInfo {
/// Create from any type implementing DeviceIdentity
pub fn from_device<D: DeviceIdentity>(device: &D) -> Self {
Self {
id: device.id(),
friendly_name: device.friendly_name().to_string(),
model_name: device.model_name().to_string(),
manufacturer: device.manufacturer().to_string(),
}
}
}
/// High-level playback state across backends.
#[derive(Clone, Debug)]
pub enum PlaybackState {
Stopped,
Playing,
Paused,
Transitioning,
NoMedia,
/// Backend-specific or unknown state string.
Unknown(String),
}
impl PlaybackState {
/// Map a raw UPnP AVTransport CurrentTransportState string
/// to a logical PlaybackState.
pub fn from_upnp_state(raw: &str) -> Self {
let s = raw.trim().to_ascii_uppercase();
match s.as_str() {
"STOPPED" => PlaybackState::Stopped,
"PLAYING" => PlaybackState::Playing,
"PAUSED_PLAYBACK" => PlaybackState::Paused,
// States from the AVTransport spec that we normalize:
"PAUSED_RECORDING" => PlaybackState::Paused,
"RECORDING" => PlaybackState::Playing,
// Common vendor-specific states:
"TRANSITIONING" => PlaybackState::Transitioning,
"BUFFERING" | "PREPARING" => PlaybackState::Transitioning,
"NO_MEDIA_PRESENT" => PlaybackState::NoMedia,
_ => PlaybackState::Unknown(raw.to_string()),
}
}
/// Returns a human-readable label for the playback state.
pub fn as_str(&self) -> &str {
match self {
PlaybackState::Stopped => "STOPPED",
PlaybackState::Playing => "PLAYING",
PlaybackState::Paused => "PAUSED",
PlaybackState::Transitioning => "TRANSITIONING",
PlaybackState::NoMedia => "NO_MEDIA",
PlaybackState::Unknown(s) => s.as_str(),
}
}
}
/// Indicates the source of the current playback.
///
/// This helps the control point distinguish between playback initiated
/// from the queue vs external sources (e.g., user playing from another app).
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum PlaybackSource {
/// No playback active or source unknown.
#[default]
None,
/// Playback was started from the control point's queue.
FromQueue,
/// Playback was started externally (e.g., from another app).
External,
}
#[derive(Clone, Debug, PartialEq)]
pub struct TrackMetadata {
pub title: Option<String>,
pub artist: Option<String>,
pub album: Option<String>,
pub genre: Option<String>,
pub album_art_uri: Option<String>,
pub date: Option<String>,
pub track_number: Option<String>,
pub creator: Option<String>,
}
#[derive(Clone, Debug, Copy)]
pub enum RendererProtocol {
UpnpAvOnly,
OpenHomeOnly,
OpenHomeHybrid,
ChromecastOnly,
}
#[derive(Clone, Debug, Default)]
pub struct RendererCapabilities {
pub has_avtransport: bool,
/// True if the renderer is known to support AVTransport.SetNextAVTransportURI.
///
/// This is discovered lazily at runtime; default is false.
pub has_avtransport_set_next: bool,
pub has_rendering_control: bool,
pub has_connection_manager: bool,
pub has_linkplay_http: bool,
pub has_arylic_tcp: bool,
pub has_oh_playlist: bool,
pub has_oh_volume: bool,
pub has_oh_info: bool,
pub has_oh_time: bool,
pub has_oh_radio: bool,
pub has_chromecast: bool,
}
impl RendererCapabilities {
pub fn make(
has_avtransport: bool,
has_avtransport_set_next: bool,
has_rendering_control: bool,
has_connection_manager: bool,
has_linkplay_http: bool,
has_arylic_tcp: bool,
has_oh_playlist: bool,
has_oh_volume: bool,
has_oh_info: bool,
has_oh_time: bool,
has_oh_radio: bool,
has_chromecast: bool,
) -> Self {
RendererCapabilities {
has_avtransport,
has_avtransport_set_next,
has_rendering_control,
has_connection_manager,
has_linkplay_http,
has_arylic_tcp,
has_oh_playlist,
has_oh_volume,
has_oh_info,
has_oh_time,
has_oh_radio,
has_chromecast,
}
}
pub fn has_avtransport(&self) -> bool {
self.has_avtransport
}
pub fn has_avtransport_set_next(&self) -> bool {
self.has_avtransport_set_next
}
pub fn has_rendering_control(&self) -> bool {
self.has_rendering_control
}
pub fn has_connection_manager(&self) -> bool {
self.has_connection_manager
}
pub fn has_linkplay_http(&self) -> bool {
self.has_linkplay_http
}
pub fn has_arylic_tcp(&self) -> bool {
self.has_arylic_tcp
}
pub fn has_oh_playlist(&self) -> bool {
self.has_oh_playlist
}
pub fn has_oh_volume(&self) -> bool {
self.has_oh_volume
}
pub fn has_oh_info(&self) -> bool {
self.has_oh_info
}
pub fn has_oh_time(&self) -> bool {
self.has_oh_time
}
pub fn has_oh_radio(&self) -> bool {
self.has_oh_radio
}
pub fn has_chromecast(&self) -> bool {
self.has_chromecast
}
pub fn supports_set_next(&self) -> bool {
self.has_avtransport && self.has_avtransport_set_next
}
}
#[derive(Clone, Debug)]
pub struct RendererInfo {
id: DeviceId,
udn: String,
friendly_name: String,
model_name: String,
manufacturer: String,
protocol: RendererProtocol,
capabilities: RendererCapabilities,
location: String,
server_header: String,
avtransport_service_type: Option<String>,
avtransport_control_url: Option<String>,
rendering_control_service_type: Option<String>,
rendering_control_control_url: Option<String>,
connection_manager_service_type: Option<String>,
connection_manager_control_url: Option<String>,
oh_playlist_service_type: Option<String>,
oh_playlist_control_url: Option<String>,
oh_playlist_event_sub_url: Option<String>,
oh_info_service_type: Option<String>,
oh_info_control_url: Option<String>,
oh_info_event_sub_url: Option<String>,
oh_time_service_type: Option<String>,
oh_time_control_url: Option<String>,
oh_time_event_sub_url: Option<String>,
oh_volume_service_type: Option<String>,
oh_volume_control_url: Option<String>,
oh_radio_service_type: Option<String>,
oh_radio_control_url: Option<String>,
oh_product_service_type: Option<String>,
oh_product_control_url: Option<String>,
}
impl RendererInfo {
pub fn make(
id: DeviceId,
udn: String,
friendly_name: String,
model_name: String,
manufacturer: String,
protocol: RendererProtocol,
capabilities: RendererCapabilities,
location: String,
server_header: String,
avtransport_service_type: Option<String>,
avtransport_control_url: Option<String>,
rendering_control_service_type: Option<String>,
rendering_control_control_url: Option<String>,
connection_manager_service_type: Option<String>,
connection_manager_control_url: Option<String>,
oh_playlist_service_type: Option<String>,
oh_playlist_control_url: Option<String>,
oh_playlist_event_sub_url: Option<String>,
oh_info_service_type: Option<String>,
oh_info_control_url: Option<String>,
oh_info_event_sub_url: Option<String>,
oh_time_service_type: Option<String>,
oh_time_control_url: Option<String>,
oh_time_event_sub_url: Option<String>,
oh_volume_service_type: Option<String>,
oh_volume_control_url: Option<String>,
oh_radio_service_type: Option<String>,
oh_radio_control_url: Option<String>,
oh_product_service_type: Option<String>,
oh_product_control_url: Option<String>,
) -> RendererInfo {
RendererInfo {
id,
udn,
friendly_name,
model_name,
manufacturer,
protocol,
capabilities,
location,
server_header,
avtransport_service_type,
avtransport_control_url,
rendering_control_service_type,
rendering_control_control_url,
connection_manager_service_type,
connection_manager_control_url,
oh_playlist_service_type,
oh_playlist_control_url,
oh_playlist_event_sub_url,
oh_info_service_type,
oh_info_control_url,
oh_info_event_sub_url,
oh_time_service_type,
oh_time_control_url,
oh_time_event_sub_url,
oh_volume_service_type,
oh_volume_control_url,
oh_radio_service_type,
oh_radio_control_url,
oh_product_service_type,
oh_product_control_url,
}
}
pub fn protocol(&self) -> RendererProtocol {
self.protocol
}
pub fn capabilities(&self) -> &RendererCapabilities {
&self.capabilities
}
pub fn avtransport_service_type(&self) -> Option<String> {
self.avtransport_service_type.clone()
}
pub fn avtransport_control_url(&self) -> Option<String> {
self.avtransport_control_url.clone()
}
pub fn rendering_control_service_type(&self) -> Option<String> {
self.rendering_control_service_type.clone()
}
pub fn rendering_control_control_url(&self) -> Option<String> {
self.rendering_control_control_url.clone()
}
pub fn connection_manager_service_type(&self) -> Option<String> {
self.connection_manager_service_type.clone()
}
pub fn connection_manager_control_url(&self) -> Option<String> {
self.connection_manager_control_url.clone()
}
pub fn oh_playlist_service_type(&self) -> Option<String> {
self.oh_playlist_service_type.clone()
}
pub fn oh_playlist_control_url(&self) -> Option<String> {
self.oh_playlist_control_url.clone()
}
pub fn oh_playlist_event_sub_url(&self) -> Option<String> {
self.oh_playlist_event_sub_url.clone()
}
pub fn oh_info_service_type(&self) -> Option<String> {
self.oh_info_service_type.clone()
}
pub fn oh_info_control_url(&self) -> Option<String> {
self.oh_info_control_url.clone()
}
pub fn oh_info_event_sub_url(&self) -> Option<String> {
self.oh_info_event_sub_url.clone()
}
pub fn oh_time_service_type(&self) -> Option<String> {
self.oh_time_service_type.clone()
}
pub fn oh_time_control_url(&self) -> Option<String> {
self.oh_time_control_url.clone()
}
pub fn oh_time_event_sub_url(&self) -> Option<String> {
self.oh_time_event_sub_url.clone()
}
pub fn oh_volume_service_type(&self) -> Option<String> {
self.oh_volume_service_type.clone()
}
pub fn oh_volume_control_url(&self) -> Option<String> {
self.oh_volume_control_url.clone()
}
pub fn oh_radio_service_type(&self) -> Option<String> {
self.oh_radio_service_type.clone()
}
pub fn oh_radio_control_url(&self) -> Option<String> {
self.oh_radio_control_url.clone()
}
pub fn oh_product_service_type(&self) -> Option<String> {
self.oh_product_service_type.clone()
}
pub fn oh_product_control_url(&self) -> Option<String> {
self.oh_product_control_url.clone()
}
}
impl DeviceIdentity for RendererInfo {
fn id(&self) -> DeviceId {
self.id.clone()
}
fn udn(&self) -> &str {
&self.udn
}
fn friendly_name(&self) -> &str {
&self.friendly_name
}
fn model_name(&self) -> &str {
&self.model_name
}
fn manufacturer(&self) -> &str {
&self.manufacturer
}
fn location(&self) -> &str {
&self.location
}
fn server_header(&self) -> &str {
&self.server_header
}
fn is_a_music_renderer(&self) -> bool {
true
}
}
#[derive(Clone, Debug)]
pub enum RendererEvent {
StateChanged {
id: DeviceId,
state: PlaybackState,
},
PositionChanged {
id: DeviceId,
position: PlaybackPositionInfo,
},
VolumeChanged {
id: DeviceId,
volume: u16,
},
MuteChanged {
id: DeviceId,
mute: bool,
},
MetadataChanged {
id: DeviceId,
metadata: TrackMetadata,
},
QueueUpdated {
id: DeviceId,
queue_length: usize,
},
BindingChanged {
id: DeviceId,
binding: Option<PlaylistBinding>,
},
Online {
id: DeviceId,
info: DeviceBasicInfo,
},
Offline {
id: DeviceId,
},
}
#[derive(Clone, Debug)]
pub enum MediaServerEvent {
GlobalUpdated {
server_id: DeviceId,
system_update_id: Option<u32>,
},
ContainersUpdated {
server_id: DeviceId,
container_ids: Vec<String>,
},
Online {
server_id: DeviceId,
info: DeviceBasicInfo,
},
Offline {
server_id: DeviceId,
},
}