Je ne sais pas trop
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -8,6 +8,8 @@
|
|||||||
**/*.o
|
**/*.o
|
||||||
**/*.o.d
|
**/*.o.d
|
||||||
**/*.a
|
**/*.a
|
||||||
|
**/*.aif
|
||||||
|
**/*.aiff
|
||||||
xxx
|
xxx
|
||||||
/dcai/
|
/dcai/
|
||||||
**/.pmomusic.yml
|
**/.pmomusic.yml
|
||||||
@@ -15,7 +17,7 @@ xxx
|
|||||||
**/.pmomusic_audio/**
|
**/.pmomusic_audio/**
|
||||||
/.pmomusic
|
/.pmomusic
|
||||||
.DS_Store
|
.DS_Store
|
||||||
/target/
|
target
|
||||||
/.pmomusic_covers
|
/.pmomusic_covers
|
||||||
/.pmomusic_audio/**
|
/.pmomusic_audio/**
|
||||||
C/src/soxr-0.1.3/Release/tests
|
C/src/soxr-0.1.3/Release/tests
|
||||||
@@ -28,3 +30,4 @@ all.txt
|
|||||||
pmo_src.txt
|
pmo_src.txt
|
||||||
upmpdcli/
|
upmpdcli/
|
||||||
/*.xml
|
/*.xml
|
||||||
|
test_upnp
|
||||||
@@ -125,6 +125,9 @@ pub struct Container {
|
|||||||
#[serde(rename = "@childCount", skip_serializing_if = "Option::is_none")]
|
#[serde(rename = "@childCount", skip_serializing_if = "Option::is_none")]
|
||||||
pub child_count: Option<String>,
|
pub child_count: Option<String>,
|
||||||
|
|
||||||
|
#[serde(rename = "@searchable", skip_serializing_if = "Option::is_none")]
|
||||||
|
pub searchable: Option<String>,
|
||||||
|
|
||||||
#[serde(rename = "dc:title", alias = "title")]
|
#[serde(rename = "dc:title", alias = "title")]
|
||||||
pub title: String,
|
pub title: String,
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,9 @@ fn to_didl_lite(containers: &[Container], items: &[pmodidl::Item]) -> Result<Str
|
|||||||
items: items.to_vec(),
|
items: items.to_vec(),
|
||||||
};
|
};
|
||||||
|
|
||||||
quick_xml::se::to_string(&didl).map_err(|e| format!("Failed to serialize DIDL-Lite: {}", e))
|
let body =
|
||||||
|
quick_xml::se::to_string(&didl).map_err(|e| format!("Failed to serialize DIDL-Lite: {}", e))?;
|
||||||
|
Ok(format!("<?xml version=\"1.0\" encoding=\"UTF-8\"?>{}", body))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handler pour le service ContentDirectory
|
/// Handler pour le service ContentDirectory
|
||||||
@@ -97,8 +99,8 @@ impl ContentHandler {
|
|||||||
if object_id == "0" {
|
if object_id == "0" {
|
||||||
// Retourner le container racine
|
// Retourner le container racine
|
||||||
let root = self.build_root_container().await;
|
let root = self.build_root_container().await;
|
||||||
let didl = to_didl_lite(&[root], &[])?;
|
let didl = to_didl_lite(&[root], &[])?;
|
||||||
Ok((didl, 1, 1, 0))
|
Ok((didl, 1, 1, 1))
|
||||||
} else {
|
} else {
|
||||||
// Essayer de trouver l'objet dans les sources
|
// Essayer de trouver l'objet dans les sources
|
||||||
// Vérifier si c'est un container racine d'une source
|
// Vérifier si c'est un container racine d'une source
|
||||||
@@ -219,6 +221,8 @@ impl ContentHandler {
|
|||||||
.root_container()
|
.root_container()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| format!("Failed to get root container: {}", e))?;
|
.map_err(|e| format!("Failed to get root container: {}", e))?;
|
||||||
|
let mut container = container;
|
||||||
|
container.searchable = container.searchable.or_else(|| Some("1".to_string()));
|
||||||
containers.push(container);
|
containers.push(container);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,7 +240,14 @@ impl ContentHandler {
|
|||||||
let returned = paginated.len();
|
let returned = paginated.len();
|
||||||
let didl = to_didl_lite(&paginated, &[])?;
|
let didl = to_didl_lite(&paginated, &[])?;
|
||||||
|
|
||||||
Ok((didl, returned as u32, total as u32, 0))
|
// Aggregate update IDs across all sources
|
||||||
|
let mut combined_id = 0u32;
|
||||||
|
for source in list_all_sources().await {
|
||||||
|
combined_id = combined_id.wrapping_add(source.update_id().await);
|
||||||
|
}
|
||||||
|
let update_id = combined_id.max(1);
|
||||||
|
|
||||||
|
Ok((didl, returned as u32, total as u32, update_id))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Browse le container racine d'une source spécifique
|
/// Browse le container racine d'une source spécifique
|
||||||
@@ -317,6 +328,7 @@ impl ContentHandler {
|
|||||||
parent_id: "-1".to_string(),
|
parent_id: "-1".to_string(),
|
||||||
restricted: Some("1".to_string()),
|
restricted: Some("1".to_string()),
|
||||||
child_count: Some(child_count.to_string()),
|
child_count: Some(child_count.to_string()),
|
||||||
|
searchable: Some("1".to_string()),
|
||||||
title: "PMOMusic".to_string(),
|
title: "PMOMusic".to_string(),
|
||||||
class: "object.container".to_string(),
|
class: "object.container".to_string(),
|
||||||
containers: vec![],
|
containers: vec![],
|
||||||
@@ -367,7 +379,19 @@ impl ContentHandler {
|
|||||||
let total = (all_containers.len() + all_items.len()) as u32;
|
let total = (all_containers.len() + all_items.len()) as u32;
|
||||||
let didl = to_didl_lite(&all_containers, &all_items)?;
|
let didl = to_didl_lite(&all_containers, &all_items)?;
|
||||||
|
|
||||||
Ok((didl, total, total, 0))
|
// Compute a global update ID from active sources, ensure it starts at 1
|
||||||
|
let update_id = if total > 0 {
|
||||||
|
let sources = list_all_sources().await;
|
||||||
|
let mut combined_id = 0u32;
|
||||||
|
for source in sources {
|
||||||
|
combined_id = combined_id.wrapping_add(source.update_id().await);
|
||||||
|
}
|
||||||
|
combined_id.max(1)
|
||||||
|
} else {
|
||||||
|
1
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((didl, total, total, update_id))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retourne les capacités de recherche
|
/// Retourne les capacités de recherche
|
||||||
@@ -392,7 +416,7 @@ impl ContentHandler {
|
|||||||
combined_id = combined_id.wrapping_add(source.update_id().await);
|
combined_id = combined_id.wrapping_add(source.update_id().await);
|
||||||
}
|
}
|
||||||
|
|
||||||
combined_id
|
combined_id.max(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -419,9 +443,10 @@ mod tests {
|
|||||||
let result = handler.browse("0", "BrowseDirectChildren", 0, 0).await;
|
let result = handler.browse("0", "BrowseDirectChildren", 0, 0).await;
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
|
|
||||||
let (didl, returned, total, _) = result.unwrap();
|
let (didl, returned, total, update_id) = result.unwrap();
|
||||||
assert_eq!(returned, 0);
|
assert_eq!(returned, 0);
|
||||||
assert_eq!(total, 0);
|
assert_eq!(total, 0);
|
||||||
|
assert_eq!(update_id, 1);
|
||||||
assert!(didl.contains("DIDL-Lite"));
|
assert!(didl.contains("DIDL-Lite"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -429,6 +454,6 @@ mod tests {
|
|||||||
async fn test_get_system_update_id() {
|
async fn test_get_system_update_id() {
|
||||||
let handler = ContentHandler::new();
|
let handler = ContentHandler::new();
|
||||||
let update_id = handler.get_system_update_id().await;
|
let update_id = handler.get_system_update_id().await;
|
||||||
assert_eq!(update_id, 0); // No sources registered
|
assert_eq!(update_id, 1); // No sources registered -> minimum 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -249,6 +249,7 @@ mod tests {
|
|||||||
parent_id: "0".to_string(),
|
parent_id: "0".to_string(),
|
||||||
restricted: Some("1".to_string()),
|
restricted: Some("1".to_string()),
|
||||||
child_count: Some("0".to_string()),
|
child_count: Some("0".to_string()),
|
||||||
|
searchable: Some("1".to_string()),
|
||||||
title: self.name.clone(),
|
title: self.name.clone(),
|
||||||
class: "object.container".to_string(),
|
class: "object.container".to_string(),
|
||||||
containers: vec![],
|
containers: vec![],
|
||||||
|
|||||||
@@ -245,6 +245,7 @@ impl RadioParadiseSource {
|
|||||||
parent_id: "0".to_string(),
|
parent_id: "0".to_string(),
|
||||||
restricted: Some("1".to_string()),
|
restricted: Some("1".to_string()),
|
||||||
child_count: Some(ALL_CHANNELS.len().to_string()),
|
child_count: Some(ALL_CHANNELS.len().to_string()),
|
||||||
|
searchable: Some("1".to_string()),
|
||||||
title: "Radio Paradise".to_string(),
|
title: "Radio Paradise".to_string(),
|
||||||
class: "object.container".to_string(),
|
class: "object.container".to_string(),
|
||||||
containers: vec![],
|
containers: vec![],
|
||||||
@@ -262,6 +263,7 @@ impl RadioParadiseSource {
|
|||||||
parent_id: "radio-paradise".to_string(),
|
parent_id: "radio-paradise".to_string(),
|
||||||
restricted: Some("1".to_string()),
|
restricted: Some("1".to_string()),
|
||||||
child_count: Some(len.to_string()),
|
child_count: Some(len.to_string()),
|
||||||
|
searchable: Some("1".to_string()),
|
||||||
title: descriptor.display_name.to_string(),
|
title: descriptor.display_name.to_string(),
|
||||||
class: "object.container.playlistContainer".to_string(),
|
class: "object.container.playlistContainer".to_string(),
|
||||||
containers: vec![],
|
containers: vec![],
|
||||||
|
|||||||
@@ -504,6 +504,7 @@ impl FifoPlaylist {
|
|||||||
parent_id: parent_id.into(),
|
parent_id: parent_id.into(),
|
||||||
restricted: Some("1".to_string()),
|
restricted: Some("1".to_string()),
|
||||||
child_count: Some(inner.queue.len().to_string()),
|
child_count: Some(inner.queue.len().to_string()),
|
||||||
|
searchable: Some("1".to_string()),
|
||||||
title: inner.title.clone(),
|
title: inner.title.clone(),
|
||||||
class: "object.container.playlistContainer".to_string(),
|
class: "object.container.playlistContainer".to_string(),
|
||||||
containers: vec![],
|
containers: vec![],
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ impl ToDIDL for Album {
|
|||||||
parent_id: parent_id.to_string(),
|
parent_id: parent_id.to_string(),
|
||||||
restricted: Some("1".to_string()),
|
restricted: Some("1".to_string()),
|
||||||
child_count: self.tracks_count.map(|c| c.to_string()),
|
child_count: self.tracks_count.map(|c| c.to_string()),
|
||||||
|
searchable: Some("1".to_string()),
|
||||||
title: self.formatted_title(),
|
title: self.formatted_title(),
|
||||||
class: "object.container.album.musicAlbum".to_string(),
|
class: "object.container.album.musicAlbum".to_string(),
|
||||||
containers: Vec::new(),
|
containers: Vec::new(),
|
||||||
@@ -134,6 +135,7 @@ impl ToDIDL for Playlist {
|
|||||||
parent_id: parent_id.to_string(),
|
parent_id: parent_id.to_string(),
|
||||||
restricted: Some("1".to_string()),
|
restricted: Some("1".to_string()),
|
||||||
child_count: self.tracks_count.map(|c| c.to_string()),
|
child_count: self.tracks_count.map(|c| c.to_string()),
|
||||||
|
searchable: Some("1".to_string()),
|
||||||
title: self.name.clone(),
|
title: self.name.clone(),
|
||||||
class: "object.container.playlistContainer".to_string(),
|
class: "object.container.playlistContainer".to_string(),
|
||||||
containers: Vec::new(),
|
containers: Vec::new(),
|
||||||
|
|||||||
@@ -275,6 +275,7 @@ impl MusicSource for QobuzSource {
|
|||||||
parent_id: "0".to_string(),
|
parent_id: "0".to_string(),
|
||||||
restricted: Some("1".to_string()),
|
restricted: Some("1".to_string()),
|
||||||
child_count: Some("2".to_string()), // Favorites + Search (simplified)
|
child_count: Some("2".to_string()), // Favorites + Search (simplified)
|
||||||
|
searchable: Some("1".to_string()),
|
||||||
title: "Qobuz".to_string(),
|
title: "Qobuz".to_string(),
|
||||||
class: "object.container".to_string(),
|
class: "object.container".to_string(),
|
||||||
containers: vec![
|
containers: vec![
|
||||||
@@ -284,6 +285,7 @@ impl MusicSource for QobuzSource {
|
|||||||
parent_id: "qobuz".to_string(),
|
parent_id: "qobuz".to_string(),
|
||||||
restricted: Some("1".to_string()),
|
restricted: Some("1".to_string()),
|
||||||
child_count: None, // Will be determined when browsed
|
child_count: None, // Will be determined when browsed
|
||||||
|
searchable: Some("1".to_string()),
|
||||||
title: "My Favorites".to_string(),
|
title: "My Favorites".to_string(),
|
||||||
class: "object.container".to_string(),
|
class: "object.container".to_string(),
|
||||||
containers: vec![],
|
containers: vec![],
|
||||||
|
|||||||
@@ -134,6 +134,8 @@ pub struct SourceRootContainer {
|
|||||||
pub class: String,
|
pub class: String,
|
||||||
/// Nombre d'enfants
|
/// Nombre d'enfants
|
||||||
pub child_count: Option<String>,
|
pub child_count: Option<String>,
|
||||||
|
/// Indique si le container est searchable ("1" ou "0")
|
||||||
|
pub searchable: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Message d'erreur
|
/// Message d'erreur
|
||||||
@@ -639,6 +641,7 @@ async fn get_source_root(Path(id): Path<String>) -> impl IntoResponse {
|
|||||||
title: container.title,
|
title: container.title,
|
||||||
class: container.class,
|
class: container.class,
|
||||||
child_count: container.child_count,
|
child_count: container.child_count,
|
||||||
|
searchable: container.searchable,
|
||||||
};
|
};
|
||||||
(StatusCode::OK, Json(root)).into_response()
|
(StatusCode::OK, Json(root)).into_response()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -962,6 +962,7 @@ mod tests {
|
|||||||
parent_id: "0".to_string(),
|
parent_id: "0".to_string(),
|
||||||
restricted: Some("1".to_string()),
|
restricted: Some("1".to_string()),
|
||||||
child_count: Some("0".to_string()),
|
child_count: Some("0".to_string()),
|
||||||
|
searchable: Some("1".to_string()),
|
||||||
title: "Test Source".to_string(),
|
title: "Test Source".to_string(),
|
||||||
class: "object.container".to_string(),
|
class: "object.container".to_string(),
|
||||||
containers: vec![],
|
containers: vec![],
|
||||||
|
|||||||
@@ -979,8 +979,7 @@ impl ServiceInstance {
|
|||||||
|
|
||||||
// Essayer de downcaster vers des types primitifs courants
|
// Essayer de downcaster vers des types primitifs courants
|
||||||
if let Some(v) = value.as_any().downcast_ref::<String>() {
|
if let Some(v) = value.as_any().downcast_ref::<String>() {
|
||||||
// Échapper les caractères XML spéciaux
|
return v.clone();
|
||||||
return escape(v).to_string();
|
|
||||||
} else if let Some(v) = value.as_any().downcast_ref::<u8>() {
|
} else if let Some(v) = value.as_any().downcast_ref::<u8>() {
|
||||||
return v.to_string();
|
return v.to_string();
|
||||||
} else if let Some(v) = value.as_any().downcast_ref::<u16>() {
|
} else if let Some(v) = value.as_any().downcast_ref::<u16>() {
|
||||||
@@ -1000,7 +999,7 @@ impl ServiceInstance {
|
|||||||
} else if let Some(v) = value.as_any().downcast_ref::<bool>() {
|
} else if let Some(v) = value.as_any().downcast_ref::<bool>() {
|
||||||
return if *v { "1" } else { "0" }.to_string();
|
return if *v { "1" } else { "0" }.to_string();
|
||||||
} else if let Some(v) = value.as_any().downcast_ref::<char>() {
|
} else if let Some(v) = value.as_any().downcast_ref::<char>() {
|
||||||
return escape(&v.to_string()).to_string();
|
return v.to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pour les structures complexes, essayer de sérialiser avec bevy_reflect
|
// Pour les structures complexes, essayer de sérialiser avec bevy_reflect
|
||||||
@@ -1345,7 +1344,7 @@ async fn control_handler(State(instance): State<Arc<ServiceInstance>>, body: Str
|
|||||||
match action_instance_for_run.run(soap_values).await {
|
match action_instance_for_run.run(soap_values).await {
|
||||||
Ok(output_data) => {
|
Ok(output_data) => {
|
||||||
// Convertir ActionData (Reflect) → HashMap<String, String> pour SOAP
|
// Convertir ActionData (Reflect) → HashMap<String, String> pour SOAP
|
||||||
let mut soap_values = HashMap::new();
|
let mut soap_values: Vec<(String, String)> = Vec::new();
|
||||||
|
|
||||||
for arg_inst in action_instance.arguments_set().all() {
|
for arg_inst in action_instance.arguments_set().all() {
|
||||||
let arg_model = arg_inst.as_ref().get_model();
|
let arg_model = arg_inst.as_ref().get_model();
|
||||||
@@ -1353,7 +1352,7 @@ async fn control_handler(State(instance): State<Arc<ServiceInstance>>, body: Str
|
|||||||
if let Some(reflect_value) = output_data.get(arg_inst.get_name()) {
|
if let Some(reflect_value) = output_data.get(arg_inst.get_name()) {
|
||||||
let soap_string =
|
let soap_string =
|
||||||
ServiceInstance::reflect_to_string(reflect_value.as_ref());
|
ServiceInstance::reflect_to_string(reflect_value.as_ref());
|
||||||
soap_values.insert(arg_inst.get_name().to_string(), soap_string);
|
soap_values.push((arg_inst.get_name().to_string(), soap_string));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
//! Construction de réponses SOAP
|
//! Construction de réponses SOAP
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use xmltree::{Element, XMLNode};
|
use xmltree::{Element, XMLNode};
|
||||||
|
|
||||||
/// Construit une réponse SOAP UPnP
|
/// Construit une réponse SOAP UPnP
|
||||||
@@ -17,13 +16,12 @@ use xmltree::{Element, XMLNode};
|
|||||||
pub fn build_soap_response(
|
pub fn build_soap_response(
|
||||||
service_urn: &str,
|
service_urn: &str,
|
||||||
action: &str,
|
action: &str,
|
||||||
values: HashMap<String, String>,
|
values: Vec<(String, String)>,
|
||||||
) -> Result<String, xmltree::Error> {
|
) -> Result<String, xmltree::Error> {
|
||||||
// Construire l'élément de réponse
|
// Construire l'élément de réponse
|
||||||
// Format: <u:ActionResponse xmlns:u="service-urn">
|
// Format: <u:ActionResponse xmlns:u="service-urn">
|
||||||
let response_name = format!("{}Response", action);
|
let response_name = format!("u:{}Response", action);
|
||||||
let mut response_elem = Element::new(&response_name);
|
let mut response_elem = Element::new(&response_name);
|
||||||
response_elem.namespace = Some(service_urn.to_string());
|
|
||||||
response_elem
|
response_elem
|
||||||
.attributes
|
.attributes
|
||||||
.insert("xmlns:u".to_string(), service_urn.to_string());
|
.insert("xmlns:u".to_string(), service_urn.to_string());
|
||||||
@@ -54,6 +52,7 @@ pub fn build_soap_response(
|
|||||||
// Sérialiser en XML
|
// Sérialiser en XML
|
||||||
let mut buf = Vec::new();
|
let mut buf = Vec::new();
|
||||||
let config = xmltree::EmitterConfig::new()
|
let config = xmltree::EmitterConfig::new()
|
||||||
|
.write_document_declaration(true)
|
||||||
.perform_indent(true)
|
.perform_indent(true)
|
||||||
.indent_string(" ");
|
.indent_string(" ");
|
||||||
envelope.write_with_config(&mut buf, config)?;
|
envelope.write_with_config(&mut buf, config)?;
|
||||||
@@ -67,9 +66,9 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_build_response() {
|
fn test_build_response() {
|
||||||
let mut values = HashMap::new();
|
let mut values = Vec::new();
|
||||||
values.insert("Track".to_string(), "5".to_string());
|
values.push(("Track".to_string(), "5".to_string()));
|
||||||
values.insert("TrackDuration".to_string(), "00:03:45".to_string());
|
values.push(("TrackDuration".to_string(), "00:03:45".to_string()));
|
||||||
|
|
||||||
let xml = build_soap_response(
|
let xml = build_soap_response(
|
||||||
"urn:schemas-upnp-org:service:AVTransport:1",
|
"urn:schemas-upnp-org:service:AVTransport:1",
|
||||||
@@ -86,7 +85,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_build_empty_response() {
|
fn test_build_empty_response() {
|
||||||
let values = HashMap::new();
|
let values = Vec::new();
|
||||||
|
|
||||||
let xml = build_soap_response("urn:schemas-upnp-org:service:AVTransport:1", "Stop", values)
|
let xml = build_soap_response("urn:schemas-upnp-org:service:AVTransport:1", "Stop", values)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|||||||
@@ -129,6 +129,7 @@ pub fn build_soap_fault(
|
|||||||
// Sérialiser
|
// Sérialiser
|
||||||
let mut buf = Vec::new();
|
let mut buf = Vec::new();
|
||||||
let config = xmltree::EmitterConfig::new()
|
let config = xmltree::EmitterConfig::new()
|
||||||
|
.write_document_declaration(true)
|
||||||
.perform_indent(true)
|
.perform_indent(true)
|
||||||
.indent_string(" ");
|
.indent_string(" ");
|
||||||
envelope.write_with_config(&mut buf, config)?;
|
envelope.write_with_config(&mut buf, config)?;
|
||||||
|
|||||||
@@ -39,8 +39,8 @@
|
|||||||
//! assert_eq!(action.args.get("InstanceID"), Some(&"0".to_string()));
|
//! assert_eq!(action.args.get("InstanceID"), Some(&"0".to_string()));
|
||||||
//!
|
//!
|
||||||
//! // Construire une réponse
|
//! // Construire une réponse
|
||||||
//! let mut values = std::collections::HashMap::new();
|
//! let mut values = Vec::new();
|
||||||
//! values.insert("CurrentTrack".to_string(), "5".to_string());
|
//! values.push(("CurrentTrack".to_string(), "5".to_string()));
|
||||||
//! let response = build_soap_response(
|
//! let response = build_soap_response(
|
||||||
//! "urn:schemas-upnp-org:service:AVTransport:1",
|
//! "urn:schemas-upnp-org:service:AVTransport:1",
|
||||||
//! "GetPositionInfo",
|
//! "GetPositionInfo",
|
||||||
|
|||||||
Reference in New Issue
Block a user