2025-10-16 22:13:00 +02:00
|
|
|
//! Exemple d'intégration avec un serveur HTTP
|
|
|
|
|
//!
|
|
|
|
|
//! Cet exemple montre comment exposer une playlist FIFO via des endpoints HTTP simples.
|
|
|
|
|
//! Dans un vrai MediaServer UPnP, ces endpoints seraient appelés par le protocole ContentDirectory.
|
|
|
|
|
//!
|
|
|
|
|
//! Pour exécuter :
|
|
|
|
|
//! ```bash
|
|
|
|
|
//! cargo run -p pmoplaylist --example http_server_integration
|
|
|
|
|
//! ```
|
|
|
|
|
|
2025-10-19 13:42:29 +02:00
|
|
|
use pmoplaylist::{DEFAULT_IMAGE, FifoPlaylist, Track};
|
2025-10-16 22:13:00 +02:00
|
|
|
use std::sync::Arc;
|
|
|
|
|
|
|
|
|
|
#[tokio::main]
|
|
|
|
|
async fn main() {
|
|
|
|
|
println!("=== Intégration HTTP Server ===\n");
|
|
|
|
|
|
|
|
|
|
// Créer une playlist partagée
|
|
|
|
|
let playlist = Arc::new(FifoPlaylist::new(
|
|
|
|
|
"my-radio".to_string(),
|
|
|
|
|
"My Internet Radio".to_string(),
|
|
|
|
|
20,
|
|
|
|
|
DEFAULT_IMAGE,
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
println!("📻 Playlist créée: {}", playlist.title().await);
|
|
|
|
|
println!("🆔 ID: {}\n", playlist.id().await);
|
|
|
|
|
|
|
|
|
|
// Ajouter quelques tracks initiaux
|
|
|
|
|
println!("📝 Ajout de tracks initiaux...");
|
|
|
|
|
let initial_tracks = vec![
|
|
|
|
|
("The Beatles", "Come Together", "Abbey Road", 259),
|
|
|
|
|
("Nirvana", "Smells Like Teen Spirit", "Nevermind", 301),
|
|
|
|
|
("Queen", "Bohemian Rhapsody", "A Night at the Opera", 354),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
for (idx, (artist, title, album, duration)) in initial_tracks.iter().enumerate() {
|
2025-10-19 13:42:29 +02:00
|
|
|
playlist
|
|
|
|
|
.append_track(
|
|
|
|
|
Track::new(
|
|
|
|
|
format!("track-{}", idx),
|
|
|
|
|
*title,
|
|
|
|
|
format!("http://media.server/music/{}.flac", idx),
|
|
|
|
|
)
|
|
|
|
|
.with_artist(*artist)
|
|
|
|
|
.with_album(*album)
|
|
|
|
|
.with_duration(*duration)
|
|
|
|
|
.with_image(format!("http://media.server/covers/{}.jpg", idx)),
|
2025-10-16 22:13:00 +02:00
|
|
|
)
|
2025-10-19 13:42:29 +02:00
|
|
|
.await;
|
2025-10-16 22:13:00 +02:00
|
|
|
println!(" ✓ {} - {}", artist, title);
|
|
|
|
|
}
|
|
|
|
|
println!();
|
|
|
|
|
|
|
|
|
|
// Simuler différents endpoints HTTP
|
|
|
|
|
|
|
|
|
|
// 1. GET /playlist/container - Retourne le container DIDL-Lite
|
|
|
|
|
println!("🌐 Endpoint: GET /playlist/container");
|
|
|
|
|
simulate_get_container(playlist.clone()).await;
|
|
|
|
|
println!();
|
|
|
|
|
|
|
|
|
|
// 2. GET /playlist/items?offset=0&count=10 - Retourne les items
|
|
|
|
|
println!("🌐 Endpoint: GET /playlist/items?offset=0&count=10");
|
|
|
|
|
simulate_get_items(playlist.clone(), 0, 10).await;
|
|
|
|
|
println!();
|
|
|
|
|
|
|
|
|
|
// 3. GET /playlist/metadata - Retourne les métadonnées
|
|
|
|
|
println!("🌐 Endpoint: GET /playlist/metadata");
|
|
|
|
|
simulate_get_metadata(playlist.clone()).await;
|
|
|
|
|
println!();
|
|
|
|
|
|
|
|
|
|
// 4. POST /playlist/track - Ajoute un nouveau track
|
|
|
|
|
println!("🌐 Endpoint: POST /playlist/track");
|
|
|
|
|
let new_track = Track::new(
|
|
|
|
|
"track-new-1",
|
|
|
|
|
"Stairway to Heaven",
|
2025-10-19 13:42:29 +02:00
|
|
|
"http://media.server/music/stairway.flac",
|
2025-10-16 22:13:00 +02:00
|
|
|
)
|
|
|
|
|
.with_artist("Led Zeppelin")
|
|
|
|
|
.with_album("Led Zeppelin IV")
|
|
|
|
|
.with_duration(482);
|
|
|
|
|
|
|
|
|
|
simulate_add_track(playlist.clone(), new_track).await;
|
|
|
|
|
println!();
|
|
|
|
|
|
|
|
|
|
// 5. DELETE /playlist/oldest - Supprime le plus ancien
|
|
|
|
|
println!("🌐 Endpoint: DELETE /playlist/oldest");
|
|
|
|
|
simulate_delete_oldest(playlist.clone()).await;
|
|
|
|
|
println!();
|
|
|
|
|
|
|
|
|
|
// 6. GET /playlist/default-image - Retourne l'image par défaut
|
|
|
|
|
println!("🌐 Endpoint: GET /playlist/default-image");
|
|
|
|
|
simulate_get_default_image(playlist.clone()).await;
|
|
|
|
|
println!();
|
|
|
|
|
|
|
|
|
|
// 7. Vérifier l'état final
|
|
|
|
|
println!("📊 État final:");
|
|
|
|
|
let final_items = playlist.get_items(0, 10).await;
|
|
|
|
|
println!(" Total tracks: {}", playlist.len().await);
|
|
|
|
|
println!(" Update ID: {}", playlist.update_id().await);
|
|
|
|
|
println!("\n Tracks actuels:");
|
|
|
|
|
for (idx, track) in final_items.iter().enumerate() {
|
|
|
|
|
let artist = track.artist.as_deref().unwrap_or("Unknown");
|
|
|
|
|
println!(" {}. {} - {}", idx + 1, artist, track.title);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
println!("\n=== Exemple terminé ===");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Simule GET /playlist/container
|
|
|
|
|
async fn simulate_get_container(playlist: Arc<FifoPlaylist>) {
|
|
|
|
|
let container = playlist.as_container().await;
|
|
|
|
|
|
|
|
|
|
println!(" Response (JSON representation):");
|
|
|
|
|
println!(" {{");
|
|
|
|
|
println!(" \"id\": \"{}\",", container.id);
|
|
|
|
|
println!(" \"parentId\": \"{}\",", container.parent_id);
|
|
|
|
|
println!(" \"title\": \"{}\",", container.title);
|
|
|
|
|
println!(" \"class\": \"{}\",", container.class);
|
2025-10-19 13:42:29 +02:00
|
|
|
println!(
|
|
|
|
|
" \"childCount\": {}",
|
|
|
|
|
container.child_count.unwrap_or_default()
|
|
|
|
|
);
|
2025-10-16 22:13:00 +02:00
|
|
|
println!(" }}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Simule GET /playlist/items?offset=X&count=Y
|
|
|
|
|
async fn simulate_get_items(playlist: Arc<FifoPlaylist>, offset: usize, count: usize) {
|
2025-10-19 13:42:29 +02:00
|
|
|
let items = playlist
|
|
|
|
|
.as_objects(offset, count, Some("http://media.server/api/default-image"))
|
|
|
|
|
.await;
|
2025-10-16 22:13:00 +02:00
|
|
|
|
|
|
|
|
println!(" Response: {} items", items.len());
|
|
|
|
|
println!(" [");
|
|
|
|
|
for (idx, item) in items.iter().enumerate() {
|
|
|
|
|
println!(" {{");
|
|
|
|
|
println!(" \"id\": \"{}\",", item.id);
|
|
|
|
|
println!(" \"title\": \"{}\",", item.title);
|
2025-10-19 13:42:29 +02:00
|
|
|
println!(
|
|
|
|
|
" \"artist\": \"{}\",",
|
|
|
|
|
item.artist.as_deref().unwrap_or("")
|
|
|
|
|
);
|
|
|
|
|
println!(
|
|
|
|
|
" \"album\": \"{}\",",
|
|
|
|
|
item.album.as_deref().unwrap_or("")
|
|
|
|
|
);
|
2025-10-16 22:13:00 +02:00
|
|
|
println!(" \"class\": \"{}\",", item.class);
|
|
|
|
|
if !item.resources.is_empty() {
|
|
|
|
|
println!(" \"uri\": \"{}\",", item.resources[0].url);
|
|
|
|
|
}
|
|
|
|
|
print!(" }}");
|
|
|
|
|
if idx < items.len() - 1 {
|
|
|
|
|
println!(",");
|
|
|
|
|
} else {
|
|
|
|
|
println!();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
println!(" ]");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Simule GET /playlist/metadata
|
|
|
|
|
async fn simulate_get_metadata(playlist: Arc<FifoPlaylist>) {
|
|
|
|
|
let update_id = playlist.update_id().await;
|
|
|
|
|
let last_change = playlist.last_change().await;
|
|
|
|
|
let count = playlist.len().await;
|
|
|
|
|
let id = playlist.id().await;
|
|
|
|
|
let title = playlist.title().await;
|
|
|
|
|
|
|
|
|
|
println!(" Response:");
|
|
|
|
|
println!(" {{");
|
|
|
|
|
println!(" \"id\": \"{}\",", id);
|
|
|
|
|
println!(" \"title\": \"{}\",", title);
|
|
|
|
|
println!(" \"trackCount\": {},", count);
|
|
|
|
|
println!(" \"updateId\": {},", update_id);
|
|
|
|
|
println!(" \"lastChange\": \"{:?}\"", last_change);
|
|
|
|
|
println!(" }}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Simule POST /playlist/track
|
|
|
|
|
async fn simulate_add_track(playlist: Arc<FifoPlaylist>, track: Track) {
|
|
|
|
|
let old_update_id = playlist.update_id().await;
|
|
|
|
|
|
|
|
|
|
playlist.append_track(track.clone()).await;
|
|
|
|
|
|
|
|
|
|
let new_update_id = playlist.update_id().await;
|
|
|
|
|
|
2025-10-19 13:42:29 +02:00
|
|
|
println!(
|
|
|
|
|
" Track added: {} - {}",
|
2025-10-16 22:13:00 +02:00
|
|
|
track.artist.as_deref().unwrap_or("Unknown"),
|
|
|
|
|
track.title
|
|
|
|
|
);
|
|
|
|
|
println!(" Update ID: {} → {}", old_update_id, new_update_id);
|
|
|
|
|
println!(" Response: 201 Created");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Simule DELETE /playlist/oldest
|
|
|
|
|
async fn simulate_delete_oldest(playlist: Arc<FifoPlaylist>) {
|
|
|
|
|
if let Some(removed) = playlist.remove_oldest().await {
|
|
|
|
|
println!(" Track removed: {} ({})", removed.title, removed.id);
|
|
|
|
|
println!(" New update ID: {}", playlist.update_id().await);
|
|
|
|
|
println!(" Response: 200 OK");
|
|
|
|
|
} else {
|
|
|
|
|
println!(" No tracks to remove");
|
|
|
|
|
println!(" Response: 404 Not Found");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Simule GET /playlist/default-image
|
|
|
|
|
async fn simulate_get_default_image(playlist: Arc<FifoPlaylist>) {
|
|
|
|
|
let image_bytes = playlist.default_image().await;
|
|
|
|
|
|
|
|
|
|
println!(" Response:");
|
|
|
|
|
println!(" Content-Type: image/webp");
|
|
|
|
|
println!(" Content-Length: {} bytes", image_bytes.len());
|
|
|
|
|
println!(" Status: 200 OK");
|
|
|
|
|
println!(" (Image WebP {} bytes ready to serve)", image_bytes.len());
|
|
|
|
|
}
|