Prise en compte des métadonnées dans PMO Contrôle
This commit is contained in:
@@ -356,6 +356,13 @@ fn playback_item_from_entry(server: &MusicServer, entry: &MediaEntry) -> Option<
|
||||
item.title = Some(entry.title.clone());
|
||||
item.server_id = Some(server.id().clone());
|
||||
item.object_id = Some(entry.id.clone());
|
||||
item.artist = entry.artist.clone();
|
||||
item.album = entry.album.clone();
|
||||
item.genre = entry.genre.clone();
|
||||
item.album_art_uri = entry.album_art_uri.clone();
|
||||
item.date = entry.date.clone();
|
||||
item.track_number = entry.track_number.clone();
|
||||
item.creator = entry.creator.clone();
|
||||
Some(item)
|
||||
}
|
||||
|
||||
@@ -431,6 +438,34 @@ fn spawn_renderer_event_thread(
|
||||
io::stdout().flush().ok();
|
||||
}
|
||||
}
|
||||
RendererEvent::MetadataChanged { id, metadata } => {
|
||||
if id == renderer_id {
|
||||
println!("\n[EVENT] Metadata changed:");
|
||||
if let Some(title) = &metadata.title {
|
||||
println!(" Title: {}", title);
|
||||
}
|
||||
if let Some(artist) = &metadata.artist {
|
||||
println!(" Artist: {}", artist);
|
||||
}
|
||||
if let Some(album) = &metadata.album {
|
||||
println!(" Album: {}", album);
|
||||
}
|
||||
if let Some(genre) = &metadata.genre {
|
||||
println!(" Genre: {}", genre);
|
||||
}
|
||||
if let Some(date) = &metadata.date {
|
||||
println!(" Date: {}", date);
|
||||
}
|
||||
if let Some(track_number) = &metadata.track_number {
|
||||
println!(" Track: {}", track_number);
|
||||
}
|
||||
if let Some(album_art_uri) = &metadata.album_art_uri {
|
||||
println!(" Album Art: {}", album_art_uri);
|
||||
}
|
||||
print!("> ");
|
||||
io::stdout().flush().ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
@@ -722,7 +757,16 @@ fn show_queue(
|
||||
} else {
|
||||
for (idx, item) in queue.iter().enumerate() {
|
||||
let title = item.title.as_deref().unwrap_or("<no title>");
|
||||
println!(" [{}] {}", idx, title);
|
||||
let artist = item.artist.as_deref().unwrap_or("");
|
||||
let album = item.album.as_deref().unwrap_or("");
|
||||
|
||||
if !artist.is_empty() && !album.is_empty() {
|
||||
println!(" [{}] {} - {} ({})", idx, artist, title, album);
|
||||
} else if !artist.is_empty() {
|
||||
println!(" [{}] {} - {}", idx, artist, title);
|
||||
} else {
|
||||
println!(" [{}] {}", idx, title);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -301,6 +301,8 @@ impl ArylicPlaybackInfo {
|
||||
} else {
|
||||
None
|
||||
},
|
||||
track_metadata: None,
|
||||
track_uri: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -391,6 +391,8 @@ pub struct PositionInfo {
|
||||
pub track_duration: Option<String>, // HH:MM:SS or None
|
||||
pub rel_time: Option<String>, // HH:MM:SS or None
|
||||
pub abs_time: Option<String>, // HH:MM:SS or None
|
||||
pub track_metadata: Option<String>, // DIDL-Lite XML or None
|
||||
pub track_uri: Option<String>, // Current track URI
|
||||
}
|
||||
|
||||
impl AvTransportClient {
|
||||
@@ -442,11 +444,15 @@ fn parse_position_info(envelope: &SoapEnvelope) -> Result<PositionInfo> {
|
||||
let track_duration = opt(response, "TrackDuration");
|
||||
let rel_time = opt(response, "RelTime");
|
||||
let abs_time = opt(response, "AbsTime");
|
||||
let track_metadata = opt(response, "TrackMetaData");
|
||||
let track_uri = opt(response, "TrackURI");
|
||||
|
||||
Ok(PositionInfo {
|
||||
track,
|
||||
track_duration,
|
||||
rel_time,
|
||||
abs_time,
|
||||
track_metadata,
|
||||
track_uri,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ pub struct PlaybackPositionInfo {
|
||||
pub rel_time: Option<String>, // position courante
|
||||
pub abs_time: Option<String>, // si pertinent
|
||||
pub track_duration: Option<String>, // durée totale
|
||||
pub track_metadata: Option<String>, // DIDL-Lite XML from GetPositionInfo
|
||||
pub track_uri: Option<String>, // Current track URI
|
||||
}
|
||||
pub trait PlaybackPosition {
|
||||
fn playback_position(&self) -> Result<PlaybackPositionInfo>;
|
||||
|
||||
@@ -14,6 +14,7 @@ use crate::capabilities::{
|
||||
PlaybackPosition, PlaybackPositionInfo, PlaybackState, PlaybackStatus, TransportControl,
|
||||
VolumeControl,
|
||||
};
|
||||
use crate::model::TrackMetadata;
|
||||
use crate::discovery::DiscoveryManager;
|
||||
use crate::events::{MediaServerEventBus, RendererEventBus};
|
||||
use crate::media_server::{
|
||||
@@ -171,6 +172,22 @@ impl ControlPoint {
|
||||
});
|
||||
}
|
||||
|
||||
// Extract and emit metadata changes
|
||||
if let Some(metadata) = extract_track_metadata(&position) {
|
||||
let metadata_changed = match prev_snapshot.last_metadata.as_ref() {
|
||||
Some(prev) => prev != &metadata,
|
||||
None => true,
|
||||
};
|
||||
|
||||
if metadata_changed {
|
||||
runtime_cp.emit_renderer_event(RendererEvent::MetadataChanged {
|
||||
id: renderer_id.clone(),
|
||||
metadata: metadata.clone(),
|
||||
});
|
||||
new_snapshot.last_metadata = Some(metadata);
|
||||
}
|
||||
}
|
||||
|
||||
new_snapshot.position = Some(position);
|
||||
}
|
||||
|
||||
@@ -601,7 +618,6 @@ impl ControlPoint {
|
||||
|
||||
let playback = (|| -> anyhow::Result<()> {
|
||||
renderer.play_uri(&item.uri, "")?;
|
||||
renderer.play()?;
|
||||
Ok(())
|
||||
})();
|
||||
|
||||
@@ -809,6 +825,7 @@ struct RendererRuntimeSnapshot {
|
||||
position: Option<PlaybackPositionInfo>,
|
||||
last_volume: Option<u16>,
|
||||
last_mute: Option<bool>,
|
||||
last_metadata: Option<TrackMetadata>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
@@ -1109,6 +1126,13 @@ fn playback_item_from_entry(server: &MusicServer, entry: &MediaEntry) -> Option<
|
||||
item.title = Some(entry.title.clone());
|
||||
item.server_id = Some(server.id().clone());
|
||||
item.object_id = Some(entry.id.clone());
|
||||
item.artist = entry.artist.clone();
|
||||
item.album = entry.album.clone();
|
||||
item.genre = entry.genre.clone();
|
||||
item.album_art_uri = entry.album_art_uri.clone();
|
||||
item.date = entry.date.clone();
|
||||
item.track_number = entry.track_number.clone();
|
||||
item.creator = entry.creator.clone();
|
||||
|
||||
Some(item)
|
||||
}
|
||||
@@ -1206,4 +1230,34 @@ fn playback_position_equal(a: &PlaybackPositionInfo, b: &PlaybackPositionInfo) -
|
||||
&& a.rel_time == b.rel_time
|
||||
&& a.abs_time == b.abs_time
|
||||
&& a.track_duration == b.track_duration
|
||||
&& a.track_metadata == b.track_metadata
|
||||
&& a.track_uri == b.track_uri
|
||||
}
|
||||
|
||||
/// Extract TrackMetadata from DIDL-Lite XML in PlaybackPositionInfo.
|
||||
fn extract_track_metadata(position: &PlaybackPositionInfo) -> Option<TrackMetadata> {
|
||||
let didl_xml = position.track_metadata.as_ref()?;
|
||||
|
||||
// Parse DIDL-Lite XML
|
||||
let didl = match pmodidl::parse_metadata::<pmodidl::DIDLLite>(didl_xml) {
|
||||
Ok(parsed) => parsed.data,
|
||||
Err(err) => {
|
||||
debug!(error = %err, "Failed to parse DIDL-Lite metadata from GetPositionInfo");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
// Extract first item metadata
|
||||
let item = didl.items.first()?;
|
||||
|
||||
Some(TrackMetadata {
|
||||
title: Some(item.title.clone()),
|
||||
artist: item.artist.clone(),
|
||||
album: item.album.clone(),
|
||||
genre: item.genre.clone(),
|
||||
album_art_uri: item.album_art.clone(),
|
||||
date: item.date.clone(),
|
||||
track_number: item.original_track_number.clone(),
|
||||
creator: item.creator.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -227,6 +227,8 @@ impl LinkPlayStatus {
|
||||
} else {
|
||||
None
|
||||
},
|
||||
track_metadata: None,
|
||||
track_uri: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,13 @@ pub struct MediaEntry {
|
||||
pub is_container: bool,
|
||||
pub class: String,
|
||||
pub resources: Vec<MediaResource>,
|
||||
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>,
|
||||
}
|
||||
|
||||
/// Backend-agnostic media browsing contract.
|
||||
@@ -323,6 +330,13 @@ fn map_didl_entries(xml: &str) -> Result<Vec<MediaEntry>> {
|
||||
is_container: true,
|
||||
class: container.class,
|
||||
resources: Vec::new(),
|
||||
artist: None,
|
||||
album: None,
|
||||
genre: None,
|
||||
album_art_uri: None,
|
||||
date: None,
|
||||
track_number: None,
|
||||
creator: None,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -344,6 +358,13 @@ fn map_didl_entries(xml: &str) -> Result<Vec<MediaEntry>> {
|
||||
is_container: false,
|
||||
class: item.class,
|
||||
resources,
|
||||
artist: item.artist,
|
||||
album: item.album,
|
||||
genre: item.genre,
|
||||
album_art_uri: item.album_art,
|
||||
date: item.date,
|
||||
track_number: item.original_track_number,
|
||||
creator: item.creator,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,18 @@ use crate::media_server::ServerId;
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct RendererId(pub String);
|
||||
|
||||
#[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)]
|
||||
pub enum RendererProtocol {
|
||||
UpnpAvOnly,
|
||||
@@ -79,6 +91,10 @@ pub enum RendererEvent {
|
||||
id: RendererId,
|
||||
mute: bool,
|
||||
},
|
||||
MetadataChanged {
|
||||
id: RendererId,
|
||||
metadata: TrackMetadata,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
||||
@@ -8,6 +8,13 @@ pub struct PlaybackItem {
|
||||
pub title: Option<String>,
|
||||
pub server_id: Option<ServerId>,
|
||||
pub object_id: 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>,
|
||||
}
|
||||
|
||||
impl PlaybackItem {
|
||||
@@ -17,6 +24,13 @@ impl PlaybackItem {
|
||||
title: None,
|
||||
server_id: None,
|
||||
object_id: None,
|
||||
artist: None,
|
||||
album: None,
|
||||
genre: None,
|
||||
album_art_uri: None,
|
||||
date: None,
|
||||
track_number: None,
|
||||
creator: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,6 +290,8 @@ impl PlaybackPosition for UpnpRenderer {
|
||||
rel_time: raw.rel_time,
|
||||
abs_time: raw.abs_time,
|
||||
track_duration: raw.track_duration,
|
||||
track_metadata: raw.track_metadata,
|
||||
track_uri: raw.track_uri,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user