Ajout de la gestion des couvertures d'albums par le FlacCacheSink
This commit is contained in:
80
old_code/pmoaudio/examples/multiroom_demo.rs
Normal file
80
old_code/pmoaudio/examples/multiroom_demo.rs
Normal file
@@ -0,0 +1,80 @@
|
||||
//! Exemple de configuration multiroom avec BufferNode
|
||||
//!
|
||||
//! Démontre l'utilisation du buffer circulaire pour synchroniser
|
||||
//! plusieurs sorties avec des délais différents
|
||||
|
||||
use pmoaudio::{BufferNode, SinkNode, SourceNode};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
println!("=== Multiroom Demo ===\n");
|
||||
|
||||
// Buffer avec capacité pour gérer les délais
|
||||
let (buffer, buffer_tx) = BufferNode::new(50, 10);
|
||||
|
||||
// Créer 3 sorties avec délais différents
|
||||
let (sink1, sink1_tx) = SinkNode::new("Room 1 (no delay)".to_string(), 10);
|
||||
let (sink2, sink2_tx) = SinkNode::new("Room 2 (5 chunks delay)".to_string(), 10);
|
||||
let (sink3, sink3_tx) = SinkNode::new("Room 3 (10 chunks delay)".to_string(), 10);
|
||||
|
||||
buffer.add_subscriber_with_offset(sink1_tx, 0).await;
|
||||
buffer.add_subscriber_with_offset(sink2_tx, 5).await;
|
||||
buffer.add_subscriber_with_offset(sink3_tx, 10).await;
|
||||
|
||||
// Spawn buffer et sinks
|
||||
tokio::spawn(async move {
|
||||
buffer.run().await.unwrap();
|
||||
});
|
||||
|
||||
let sink1_handle = tokio::spawn(async move {
|
||||
let stats = sink1.run_with_stats().await.unwrap();
|
||||
stats.display();
|
||||
stats
|
||||
});
|
||||
|
||||
let sink2_handle = tokio::spawn(async move {
|
||||
let stats = sink2.run_with_stats().await.unwrap();
|
||||
stats.display();
|
||||
stats
|
||||
});
|
||||
|
||||
let sink3_handle = tokio::spawn(async move {
|
||||
let stats = sink3.run_with_stats().await.unwrap();
|
||||
stats.display();
|
||||
stats
|
||||
});
|
||||
|
||||
// Générer de l'audio dans une tâche séparée
|
||||
println!("Generating audio for multiroom playback...\n");
|
||||
tokio::spawn(async move {
|
||||
let mut source = SourceNode::new();
|
||||
source.add_subscriber(buffer_tx);
|
||||
source
|
||||
.generate_chunks(30, 4800, 48000, 440.0)
|
||||
.await
|
||||
.unwrap();
|
||||
});
|
||||
|
||||
println!("Waiting for all rooms to finish...\n");
|
||||
|
||||
// Attendre toutes les sorties
|
||||
let stats1 = sink1_handle.await.unwrap();
|
||||
let stats2 = sink2_handle.await.unwrap();
|
||||
let stats3 = sink3_handle.await.unwrap();
|
||||
|
||||
println!("\n=== Multiroom Summary ===");
|
||||
println!(
|
||||
"{}: {} chunks received",
|
||||
stats1.name, stats1.chunks_received
|
||||
);
|
||||
println!(
|
||||
"{}: {} chunks received",
|
||||
stats2.name, stats2.chunks_received
|
||||
);
|
||||
println!(
|
||||
"{}: {} chunks received",
|
||||
stats3.name, stats3.chunks_received
|
||||
);
|
||||
|
||||
println!("\nNote: Delayed rooms receive fewer chunks due to the offset");
|
||||
}
|
||||
168
old_code/pmoaudio/examples/multiroom_volume_demo.rs
Normal file
168
old_code/pmoaudio/examples/multiroom_volume_demo.rs
Normal file
@@ -0,0 +1,168 @@
|
||||
//! Exemple complet de pipeline multiroom avec contrôle de volume
|
||||
//!
|
||||
//! Ce programme démontre :
|
||||
//! - Une source audio unique
|
||||
//! - Deux branches de sortie : Chromecast et DiskSink
|
||||
//! - Un volume master avec deux VolumeNodes secondaires synchronisés
|
||||
//! - Système d'événements pour la communication entre nodes
|
||||
|
||||
use pmoaudio::{
|
||||
ChromecastConfig, ChromecastSink, DiskSink, DiskSinkConfig, SourceNode, VolumeNode,
|
||||
};
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("=== PMOAudio Multiroom Volume Demo ===\n");
|
||||
|
||||
// Configuration
|
||||
let sample_rate = 48000u32;
|
||||
let chunk_size = 4800usize; // 100ms à 48kHz
|
||||
let num_chunks = 50; // 5 secondes de lecture
|
||||
let frequency = 440.0; // La 440 Hz
|
||||
|
||||
// ===== 1. Créer la source audio =====
|
||||
println!("1. Creating audio source...");
|
||||
let mut source = SourceNode::new();
|
||||
|
||||
// ===== 2. Créer le volume master =====
|
||||
println!("2. Creating master volume node...");
|
||||
let (mut master_volume, master_tx) = VolumeNode::new("master".to_string(), 1.0, 50);
|
||||
let master_handle = master_volume.get_handle();
|
||||
|
||||
// Channel pour les événements du volume master
|
||||
let (master_event_tx, master_event_rx_chromecast) = mpsc::channel(10);
|
||||
let (_, master_event_rx_disk) = mpsc::channel(10);
|
||||
|
||||
master_volume.subscribe_volume_events(master_event_tx);
|
||||
|
||||
source.add_subscriber(master_tx);
|
||||
|
||||
// ===== 3. Créer les branches de sortie =====
|
||||
|
||||
// Branche 1: Chromecast avec volume secondaire
|
||||
println!("3a. Creating Chromecast output branch...");
|
||||
let (mut chromecast_volume, chromecast_volume_tx) =
|
||||
VolumeNode::new("chromecast_volume".to_string(), 0.8, 50);
|
||||
|
||||
chromecast_volume.set_master_volume_source(master_event_rx_chromecast);
|
||||
|
||||
let chromecast_config = ChromecastConfig {
|
||||
device_address: "192.168.1.100".to_string(),
|
||||
device_name: "Living Room".to_string(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let (chromecast_sink, chromecast_sink_tx) =
|
||||
ChromecastSink::new("chromecast1".to_string(), chromecast_config, 50);
|
||||
|
||||
chromecast_volume.add_subscriber(chromecast_sink_tx);
|
||||
master_volume.add_subscriber(chromecast_volume_tx);
|
||||
|
||||
// Branche 2: DiskSink avec volume secondaire
|
||||
println!("3b. Creating DiskSink output branch...");
|
||||
let (mut disk_volume, disk_volume_tx) = VolumeNode::new("disk_volume".to_string(), 0.9, 50);
|
||||
|
||||
disk_volume.set_master_volume_source(master_event_rx_disk);
|
||||
|
||||
let disk_config = DiskSinkConfig {
|
||||
output_dir: std::env::temp_dir().join("pmoaudio_demo"),
|
||||
filename: Some("multiroom_output.wav".to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let (disk_sink, disk_sink_tx) = DiskSink::new("disk1".to_string(), disk_config, 50);
|
||||
|
||||
disk_volume.add_subscriber(disk_sink_tx);
|
||||
master_volume.add_subscriber(disk_volume_tx);
|
||||
|
||||
// ===== 4. Lancer tous les nodes =====
|
||||
println!("4. Starting pipeline nodes...\n");
|
||||
|
||||
// Spawn master volume
|
||||
let master_volume_handle = tokio::spawn(async move {
|
||||
master_volume.run().await.unwrap();
|
||||
});
|
||||
|
||||
// Spawn chromecast branch
|
||||
let chromecast_volume_handle = tokio::spawn(async move {
|
||||
chromecast_volume.run().await.unwrap();
|
||||
});
|
||||
|
||||
let chromecast_sink_handle = tokio::spawn(async move {
|
||||
let stats = chromecast_sink.run().await.unwrap();
|
||||
stats.display();
|
||||
});
|
||||
|
||||
// Spawn disk branch
|
||||
let disk_volume_handle = tokio::spawn(async move {
|
||||
disk_volume.run().await.unwrap();
|
||||
});
|
||||
|
||||
let disk_sink_handle = tokio::spawn(async move {
|
||||
let stats = disk_sink.run().await.unwrap();
|
||||
stats.display();
|
||||
});
|
||||
|
||||
// ===== 5. Contrôler le volume pendant la lecture =====
|
||||
let master_handle_clone = master_handle.clone();
|
||||
tokio::spawn(async move {
|
||||
// Attendre un peu, puis diminuer le volume
|
||||
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
|
||||
println!("\n>>> Decreasing master volume to 0.7");
|
||||
master_handle_clone.set_volume(0.7).await;
|
||||
|
||||
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
|
||||
println!(">>> Decreasing master volume to 0.4");
|
||||
master_handle_clone.set_volume(0.4).await;
|
||||
|
||||
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
|
||||
println!(">>> Increasing master volume back to 1.0");
|
||||
master_handle_clone.set_volume(1.0).await;
|
||||
});
|
||||
|
||||
// ===== 6. Générer et envoyer les chunks audio =====
|
||||
println!("5. Generating and streaming audio...");
|
||||
tokio::spawn(async move {
|
||||
source
|
||||
.generate_chunks(num_chunks, chunk_size, sample_rate, frequency)
|
||||
.await
|
||||
.unwrap();
|
||||
println!("\n>>> Audio generation complete!");
|
||||
});
|
||||
|
||||
// ===== 7. Attendre la fin de tous les nodes =====
|
||||
println!("6. Waiting for all nodes to complete...\n");
|
||||
|
||||
// Attendre que les sinks terminent
|
||||
chromecast_sink_handle.await?;
|
||||
disk_sink_handle.await?;
|
||||
|
||||
// Nettoyer
|
||||
master_volume_handle.abort();
|
||||
chromecast_volume_handle.abort();
|
||||
disk_volume_handle.abort();
|
||||
|
||||
println!("\n=== Demo completed successfully! ===");
|
||||
println!("\nSummary:");
|
||||
println!(
|
||||
"- Generated {} chunks of {} samples each",
|
||||
num_chunks, chunk_size
|
||||
);
|
||||
println!(
|
||||
"- Total duration: {:.2} seconds",
|
||||
(num_chunks as usize * chunk_size) as f32 / sample_rate as f32
|
||||
);
|
||||
println!("- Output to Chromecast: Living Room (192.168.1.100)");
|
||||
println!(
|
||||
"- Output to file: {}",
|
||||
std::env::temp_dir()
|
||||
.join("pmoaudio_demo")
|
||||
.join("multiroom_output.wav")
|
||||
.display()
|
||||
);
|
||||
println!("- Master volume control demonstrated with live changes");
|
||||
println!("\nAll streams received synchronized volume updates!");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
126
old_code/pmoaudio/examples/pipeline_demo.rs
Normal file
126
old_code/pmoaudio/examples/pipeline_demo.rs
Normal file
@@ -0,0 +1,126 @@
|
||||
//! Exemple de pipeline audio stéréo complet avec tous les nodes
|
||||
//!
|
||||
//! Pipeline: SourceNode → DecoderNode → DspNode → BufferNode → TimerNode → SinkNode(s)
|
||||
|
||||
use pmoaudio::{BufferNode, DecoderNode, DspNode, SinkNode, SourceNode, TimerNode};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
println!("=== PMOAudio Pipeline Demo ===\n");
|
||||
|
||||
// Créer le pipeline de nodes
|
||||
|
||||
// 2. DecoderNode - passthrough dans cet exemple
|
||||
let (mut decoder, decoder_tx) = DecoderNode::new(10);
|
||||
|
||||
// 3. DspNode - applique un gain de 0.5
|
||||
let (mut dsp, dsp_tx) = DspNode::new(10, 0.5);
|
||||
|
||||
// 4. BufferNode - buffer circulaire pour multiroom
|
||||
let (mut buffer, buffer_tx) = BufferNode::new(100, 10);
|
||||
|
||||
// 5. TimerNode - calcule la position temporelle
|
||||
let (mut timer, timer_tx) = TimerNode::new(10);
|
||||
|
||||
// 6. SinkNodes - deux destinations finales
|
||||
let (sink1, sink1_tx) = SinkNode::new("Main Output".to_string(), 10);
|
||||
let (sink2, sink2_tx) = SinkNode::new("Secondary Output".to_string(), 10);
|
||||
|
||||
// Ajouter un abonné au BufferNode avec offset (multiroom simulation)
|
||||
let (sink3, sink3_tx) = SinkNode::new("Delayed Output".to_string(), 10);
|
||||
buffer.add_subscriber_with_offset(sink3_tx, 5).await; // 5 chunks de retard
|
||||
|
||||
// Connecter le pipeline
|
||||
decoder.add_subscriber(dsp_tx);
|
||||
dsp.add_subscriber(buffer_tx);
|
||||
buffer.add_next_subscriber(timer_tx); // BufferNode -> TimerNode
|
||||
timer.add_subscriber(sink1_tx);
|
||||
timer.add_subscriber(sink2_tx);
|
||||
|
||||
// Obtenir un handle pour lire la position du TimerNode
|
||||
let timer_handle = timer.get_position_handle();
|
||||
|
||||
// Spawn tous les nodes
|
||||
let decoder_handle = tokio::spawn(async move {
|
||||
decoder.run_passthrough().await.unwrap();
|
||||
});
|
||||
|
||||
let dsp_handle = tokio::spawn(async move {
|
||||
dsp.run().await.unwrap();
|
||||
});
|
||||
|
||||
let buffer_handle = tokio::spawn(async move {
|
||||
buffer.run().await.unwrap();
|
||||
});
|
||||
|
||||
let timer_handle_task = tokio::spawn(async move {
|
||||
timer.run().await.unwrap();
|
||||
});
|
||||
|
||||
let sink1_handle = tokio::spawn(async move {
|
||||
let stats = sink1.run_with_stats().await.unwrap();
|
||||
stats.display();
|
||||
stats
|
||||
});
|
||||
|
||||
let sink2_handle = tokio::spawn(async move {
|
||||
sink2.run_silent().await.unwrap();
|
||||
});
|
||||
|
||||
let sink3_handle = tokio::spawn(async move {
|
||||
let stats = sink3.run_with_stats().await.unwrap();
|
||||
stats.display();
|
||||
stats
|
||||
});
|
||||
|
||||
// Spawn une tâche pour afficher la position périodiquement
|
||||
let position_monitor = tokio::spawn(async move {
|
||||
for _ in 0..10 {
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
|
||||
let position = timer_handle.position_sec().await;
|
||||
let samples = timer_handle.elapsed_samples().await;
|
||||
println!("Position: {:.3} sec ({} samples)", position, samples);
|
||||
}
|
||||
});
|
||||
|
||||
// Générer des chunks audio
|
||||
println!("Generating audio chunks...\n");
|
||||
let chunk_size = 4800; // 100ms à 48kHz
|
||||
let sample_rate = 48000;
|
||||
let frequency = 440.0; // La 440Hz
|
||||
|
||||
// Source node dans une tâche séparée
|
||||
tokio::spawn(async move {
|
||||
let mut source = SourceNode::new();
|
||||
source.add_subscriber(decoder_tx);
|
||||
|
||||
// Générer 50 chunks (environ 5 secondes)
|
||||
source
|
||||
.generate_chunks(50, chunk_size, sample_rate, frequency)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
println!("\nChunks sent. Processing...\n");
|
||||
});
|
||||
|
||||
// Attendre que tous les nodes terminent
|
||||
decoder_handle.await.unwrap();
|
||||
dsp_handle.await.unwrap();
|
||||
buffer_handle.await.unwrap();
|
||||
timer_handle_task.await.unwrap();
|
||||
|
||||
let stats1 = sink1_handle.await.unwrap();
|
||||
sink2_handle.await.unwrap();
|
||||
let stats3 = sink3_handle.await.unwrap();
|
||||
position_monitor.await.unwrap();
|
||||
|
||||
println!("\n=== Pipeline Demo Complete ===");
|
||||
println!(
|
||||
"Main output processed: {} chunks, {:.3} sec",
|
||||
stats1.chunks_received, stats1.total_duration_sec
|
||||
);
|
||||
println!(
|
||||
"Delayed output processed: {} chunks, {:.3} sec",
|
||||
stats3.chunks_received, stats3.total_duration_sec
|
||||
);
|
||||
}
|
||||
96
old_code/pmoaudio/examples/quick_start.rs
Normal file
96
old_code/pmoaudio/examples/quick_start.rs
Normal file
@@ -0,0 +1,96 @@
|
||||
//! Quick Start - Démonstration rapide des nouvelles fonctionnalités
|
||||
//!
|
||||
//! Cet exemple montre l'utilisation des principales nouvelles fonctionnalités :
|
||||
//! - VolumeNode avec contrôle dynamique
|
||||
//! - DiskSink pour écriture sur disque
|
||||
//! - Pipeline simple et efficace
|
||||
|
||||
use pmoaudio::{AudioFileFormat, DiskSink, DiskSinkConfig, SourceNode, VolumeNode};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("=== PMOAudio Quick Start ===\n");
|
||||
|
||||
// 1. Créer la source audio (génère un signal de test)
|
||||
let mut source = SourceNode::new();
|
||||
|
||||
// 2. Créer un VolumeNode pour contrôler le volume
|
||||
let (mut volume, volume_tx) = VolumeNode::new("main".to_string(), 0.8, 10);
|
||||
let volume_handle = volume.get_handle();
|
||||
|
||||
// 3. Créer un DiskSink pour écrire sur disque
|
||||
let output_dir = std::env::temp_dir().join("pmoaudio_quickstart");
|
||||
let config = DiskSinkConfig {
|
||||
output_dir: output_dir.clone(),
|
||||
filename: Some("quickstart_output.wav".to_string()),
|
||||
format: AudioFileFormat::Wav,
|
||||
buffer_size: 50,
|
||||
};
|
||||
|
||||
let (disk_sink, disk_tx) = DiskSink::new("disk".to_string(), config, 10);
|
||||
|
||||
// 4. Connecter le pipeline : Source → Volume → DiskSink
|
||||
source.add_subscriber(volume_tx);
|
||||
volume.add_subscriber(disk_tx);
|
||||
|
||||
println!("Pipeline configured:");
|
||||
println!(" SourceNode → VolumeNode (vol=0.8) → DiskSink");
|
||||
println!(" Output: {}/quickstart_output.wav\n", output_dir.display());
|
||||
|
||||
// 5. Lancer les nodes
|
||||
let volume_handle_clone = volume_handle.clone();
|
||||
tokio::spawn(async move {
|
||||
volume.run().await.unwrap();
|
||||
});
|
||||
|
||||
let disk_handle = tokio::spawn(async move {
|
||||
let stats = disk_sink.run().await.unwrap();
|
||||
println!("\nDiskSink Statistics:");
|
||||
stats.display();
|
||||
stats
|
||||
});
|
||||
|
||||
// 6. Démonstration du contrôle de volume pendant la lecture
|
||||
tokio::spawn(async move {
|
||||
println!("Generating audio with volume changes...");
|
||||
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
|
||||
println!(" → Volume: 0.8 (initial)");
|
||||
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
|
||||
volume_handle_clone.set_volume(0.5).await;
|
||||
println!(" → Volume: 0.5 (decreased)");
|
||||
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
|
||||
volume_handle_clone.set_volume(1.0).await;
|
||||
println!(" → Volume: 1.0 (maximum)");
|
||||
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
|
||||
volume_handle_clone.set_volume(0.3).await;
|
||||
println!(" → Volume: 0.3 (low)");
|
||||
});
|
||||
|
||||
// 7. Générer l'audio (10 chunks de 4800 samples à 48kHz = ~1 seconde)
|
||||
source
|
||||
.generate_chunks(
|
||||
10, // nombre de chunks
|
||||
4800, // samples par chunk (100ms @ 48kHz)
|
||||
48000, // sample rate
|
||||
440.0, // fréquence (La 440 Hz)
|
||||
)
|
||||
.await?;
|
||||
|
||||
// 8. Attendre la fin du traitement
|
||||
let stats = disk_handle.await?;
|
||||
|
||||
// 9. Résumé
|
||||
println!("\n=== Summary ===");
|
||||
println!("✓ Audio file generated successfully");
|
||||
println!("✓ {} chunks written", stats.chunks_written);
|
||||
println!("✓ Duration: {:.2} seconds", stats.total_duration_sec);
|
||||
println!("✓ Volume was dynamically adjusted during playback");
|
||||
println!("\nYou can play the file with:");
|
||||
println!(" ffplay {}/quickstart_output.wav", output_dir.display());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
53
old_code/pmoaudio/examples/simple_pipeline.rs
Normal file
53
old_code/pmoaudio/examples/simple_pipeline.rs
Normal file
@@ -0,0 +1,53 @@
|
||||
//! Exemple simple de pipeline audio : Source → Timer → Sink
|
||||
//!
|
||||
//! Démontre l'utilisation basique du pipeline avec calcul de position
|
||||
|
||||
use pmoaudio::{SinkNode, SourceNode, TimerNode};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
println!("=== Simple Pipeline Example ===\n");
|
||||
|
||||
// Créer les nodes
|
||||
let (mut timer, timer_tx) = TimerNode::new(10);
|
||||
let (sink, sink_tx) = SinkNode::new("Output".to_string(), 10);
|
||||
|
||||
// Connecter
|
||||
timer.add_subscriber(sink_tx);
|
||||
|
||||
// Handle pour monitorer la position
|
||||
let timer_handle = timer.get_position_handle();
|
||||
|
||||
// Spawn timer et sink
|
||||
tokio::spawn(async move {
|
||||
timer.run().await.unwrap();
|
||||
});
|
||||
|
||||
let sink_handle = tokio::spawn(async move {
|
||||
let stats = sink.run_with_stats().await.unwrap();
|
||||
stats.display();
|
||||
stats
|
||||
});
|
||||
|
||||
// Générer quelques secondes d'audio dans une tâche séparée
|
||||
println!("Generating 440Hz sine wave...\n");
|
||||
|
||||
tokio::spawn(async move {
|
||||
let mut source = SourceNode::new();
|
||||
source.add_subscriber(timer_tx);
|
||||
|
||||
source
|
||||
.generate_chunks(30, 4800, 48000, 440.0) // ~3 secondes
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// La source est drop ici, fermant le channel
|
||||
});
|
||||
|
||||
// Attendre la fin
|
||||
let stats = sink_handle.await.unwrap();
|
||||
|
||||
let final_position = timer_handle.position_sec().await;
|
||||
println!("\nFinal position: {:.3} seconds", final_position);
|
||||
println!("Total duration: {:.3} seconds", stats.total_duration_sec);
|
||||
}
|
||||
52
old_code/pmoaudio/examples/streaming_demo.rs
Normal file
52
old_code/pmoaudio/examples/streaming_demo.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
//! Exemple de streaming audio en temps réel
|
||||
//!
|
||||
//! Démontre l'utilisation du pipeline avec génération de chunks
|
||||
//! en temps réel avec timing approprié
|
||||
|
||||
use pmoaudio::{SinkNode, SourceNode, TimerNode};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
println!("=== Streaming Demo ===\n");
|
||||
println!("Streaming audio in real-time for 3 seconds...\n");
|
||||
|
||||
let mut source = SourceNode::new();
|
||||
let (mut timer, timer_tx) = TimerNode::new(20);
|
||||
let (sink, sink_tx) = SinkNode::new("Streaming Output".to_string(), 20);
|
||||
|
||||
source.add_subscriber(timer_tx);
|
||||
timer.add_subscriber(sink_tx);
|
||||
|
||||
let timer_handle = timer.get_position_handle();
|
||||
|
||||
// Spawn le pipeline
|
||||
tokio::spawn(async move {
|
||||
timer.run().await.unwrap();
|
||||
});
|
||||
|
||||
let sink_handle = tokio::spawn(async move {
|
||||
sink.run_with_logging().await.unwrap();
|
||||
});
|
||||
|
||||
// Monitor la position
|
||||
let monitor_handle = tokio::spawn(async move {
|
||||
for _ in 0..15 {
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
|
||||
let position = timer_handle.position_sec().await;
|
||||
println!("Playback position: {:.3} sec", position);
|
||||
}
|
||||
});
|
||||
|
||||
// Stream des chunks avec timing réel
|
||||
// 100ms par chunk à 48kHz = 4800 samples
|
||||
source
|
||||
.stream_chunks(4800, 48000, 440.0, 3000) // 3 secondes
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
println!("\nStreaming complete.");
|
||||
|
||||
// Attendre la fin
|
||||
sink_handle.await.unwrap();
|
||||
monitor_handle.await.unwrap();
|
||||
}
|
||||
58
old_code/pmoaudio/examples/volume_control_demo.rs
Normal file
58
old_code/pmoaudio/examples/volume_control_demo.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
//! Exemple simple de contrôle de volume
|
||||
//!
|
||||
//! Démontre l'utilisation du VolumeNode avec changements dynamiques
|
||||
|
||||
use pmoaudio::{SinkNode, SourceNode, VolumeNode};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("=== Volume Control Demo ===\n");
|
||||
|
||||
// Créer la source
|
||||
let mut source = SourceNode::new();
|
||||
|
||||
// Créer le volume node
|
||||
let (mut volume, volume_tx) = VolumeNode::new("main".to_string(), 1.0, 10);
|
||||
let volume_handle = volume.get_handle();
|
||||
|
||||
// Créer le sink
|
||||
let (sink, sink_tx) = SinkNode::new("Output".to_string(), 10);
|
||||
|
||||
// Connecter le pipeline
|
||||
source.add_subscriber(volume_tx);
|
||||
volume.add_subscriber(sink_tx);
|
||||
|
||||
// Lancer les nodes
|
||||
tokio::spawn(async move { volume.run().await.unwrap() });
|
||||
|
||||
let sink_handle = tokio::spawn(async move { sink.run_with_stats().await.unwrap() });
|
||||
|
||||
// Contrôler le volume pendant la lecture
|
||||
let volume_control = tokio::spawn(async move {
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
|
||||
println!("Setting volume to 0.5");
|
||||
volume_handle.set_volume(0.5).await;
|
||||
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
|
||||
println!("Setting volume to 0.2");
|
||||
volume_handle.set_volume(0.2).await;
|
||||
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
|
||||
println!("Setting volume to 1.0");
|
||||
volume_handle.set_volume(1.0).await;
|
||||
});
|
||||
|
||||
// Générer l'audio
|
||||
source
|
||||
.generate_chunks(20, 4800, 48000, 440.0)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
volume_control.await?;
|
||||
let stats = sink_handle.await?;
|
||||
|
||||
println!("\nFinal statistics:");
|
||||
stats.display();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
170
old_code/pmoplaylist/examples/basic_usage.rs
Normal file
170
old_code/pmoplaylist/examples/basic_usage.rs
Normal file
@@ -0,0 +1,170 @@
|
||||
//! Exemple d'utilisation basique de pmoplaylist
|
||||
//!
|
||||
//! Pour exécuter cet exemple :
|
||||
//! ```bash
|
||||
//! cargo run -p pmoplaylist --example basic_usage
|
||||
//! ```
|
||||
|
||||
use pmoplaylist::{FifoPlaylist, Track, DEFAULT_IMAGE};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
println!("=== Exemple pmoplaylist ===\n");
|
||||
|
||||
// 1. Créer une playlist FIFO
|
||||
println!("1. Création d'une playlist avec capacité de 5 tracks...");
|
||||
let playlist = FifoPlaylist::new(
|
||||
"my-radio".to_string(),
|
||||
"Ma Radio Préférée".to_string(),
|
||||
5,
|
||||
DEFAULT_IMAGE,
|
||||
);
|
||||
println!(" ✓ Playlist créée: {}", playlist.title().await);
|
||||
println!(" ✓ ID: {}", playlist.id().await);
|
||||
println!(" ✓ Capacité: 5 tracks");
|
||||
println!(" ✓ Update ID initial: {}\n", playlist.update_id().await);
|
||||
|
||||
// 2. Ajouter des tracks
|
||||
println!("2. Ajout de 3 tracks...");
|
||||
let tracks = vec![
|
||||
Track::new(
|
||||
"track-1",
|
||||
"Bohemian Rhapsody",
|
||||
"http://example.com/queen/bohemian.flac",
|
||||
)
|
||||
.with_artist("Queen")
|
||||
.with_album("A Night at the Opera")
|
||||
.with_duration(354)
|
||||
.with_image("http://example.com/covers/queen-anato.jpg"),
|
||||
Track::new(
|
||||
"track-2",
|
||||
"Stairway to Heaven",
|
||||
"http://example.com/zeppelin/stairway.mp3",
|
||||
)
|
||||
.with_artist("Led Zeppelin")
|
||||
.with_album("Led Zeppelin IV")
|
||||
.with_duration(482),
|
||||
Track::new(
|
||||
"track-3",
|
||||
"Hotel California",
|
||||
"http://example.com/eagles/hotel.flac",
|
||||
)
|
||||
.with_artist("Eagles")
|
||||
.with_album("Hotel California")
|
||||
.with_duration(391),
|
||||
];
|
||||
|
||||
for track in tracks {
|
||||
playlist.append_track(track.clone()).await;
|
||||
println!(
|
||||
" ✓ Ajouté: {} - {}",
|
||||
track.title,
|
||||
track.artist.unwrap_or_default()
|
||||
);
|
||||
}
|
||||
|
||||
println!("\n Total tracks: {}", playlist.len().await);
|
||||
println!(" Update ID: {}\n", playlist.update_id().await);
|
||||
|
||||
// 3. Tester le comportement FIFO
|
||||
println!("3. Test du comportement FIFO (capacité = 5)...");
|
||||
println!(" Ajout de 4 tracks supplémentaires...");
|
||||
|
||||
for i in 4..=7 {
|
||||
let track = Track::new(
|
||||
format!("track-{}", i),
|
||||
format!("Song Number {}", i),
|
||||
format!("http://example.com/songs/{}.mp3", i),
|
||||
);
|
||||
playlist.append_track(track).await;
|
||||
}
|
||||
|
||||
println!(
|
||||
" ✓ Total tracks (limité par capacité): {}",
|
||||
playlist.len().await
|
||||
);
|
||||
|
||||
// Afficher les tracks actuels
|
||||
let items = playlist.get_items(0, 10).await;
|
||||
println!("\n Tracks actuels dans la FIFO:");
|
||||
for (idx, track) in items.iter().enumerate() {
|
||||
println!(" {}. {} ({})", idx + 1, track.title, track.id);
|
||||
}
|
||||
println!(" (Les tracks 1 et 2 ont été supprimés automatiquement)\n");
|
||||
|
||||
// 4. Supprimer le plus ancien
|
||||
println!("4. Suppression du track le plus ancien...");
|
||||
if let Some(removed) = playlist.remove_oldest().await {
|
||||
println!(" ✓ Supprimé: {} ({})", removed.title, removed.id);
|
||||
}
|
||||
println!(" Total tracks: {}", playlist.len().await);
|
||||
println!(" Update ID: {}\n", playlist.update_id().await);
|
||||
|
||||
// 5. Supprimer par ID
|
||||
println!("5. Suppression d'un track par ID (track-5)...");
|
||||
if playlist.remove_by_id("track-5").await {
|
||||
println!(" ✓ Track supprimé");
|
||||
}
|
||||
println!(" Total tracks: {}", playlist.len().await);
|
||||
println!(" Update ID: {}\n", playlist.update_id().await);
|
||||
|
||||
// 6. Générer un Container DIDL-Lite
|
||||
println!("6. Génération du Container DIDL-Lite...");
|
||||
let container = playlist.as_container().await;
|
||||
println!(" Container:");
|
||||
println!(" - ID: {}", container.id);
|
||||
println!(" - Parent ID: {}", container.parent_id);
|
||||
println!(" - Title: {}", container.title);
|
||||
println!(" - Class: {}", container.class);
|
||||
println!(
|
||||
" - Child Count: {}\n",
|
||||
container.child_count.unwrap_or_default()
|
||||
);
|
||||
|
||||
// 7. Générer des Items DIDL-Lite
|
||||
println!("7. Génération des Items DIDL-Lite...");
|
||||
let didl_items = playlist
|
||||
.as_objects(0, 10, Some("http://myserver/api/default-image"))
|
||||
.await;
|
||||
|
||||
println!(" Items DIDL-Lite:");
|
||||
for (idx, item) in didl_items.iter().enumerate() {
|
||||
println!("\n Item {}:", idx + 1);
|
||||
println!(" - ID: {}", item.id);
|
||||
println!(" - Title: {}", item.title);
|
||||
println!(" - Artist: {}", item.artist.as_deref().unwrap_or("N/A"));
|
||||
println!(" - Album: {}", item.album.as_deref().unwrap_or("N/A"));
|
||||
println!(" - Class: {}", item.class);
|
||||
println!(" - Parent ID: {}", item.parent_id);
|
||||
|
||||
if !item.resources.is_empty() {
|
||||
println!(" - Resource URI: {}", item.resources[0].url);
|
||||
if let Some(ref duration) = item.resources[0].duration {
|
||||
println!(" - Duration: {}", duration);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ref art) = item.album_art {
|
||||
println!(" - Album Art: {}", art);
|
||||
}
|
||||
}
|
||||
|
||||
// 8. Image par défaut
|
||||
println!("\n8. Image par défaut...");
|
||||
let default_image = playlist.default_image().await;
|
||||
println!(
|
||||
" ✓ Taille de l'image par défaut: {} bytes",
|
||||
default_image.len()
|
||||
);
|
||||
println!(" (Cette image peut être servie via un endpoint HTTP)\n");
|
||||
|
||||
// 9. Vider la playlist
|
||||
println!("9. Vidage de la playlist...");
|
||||
playlist.clear().await;
|
||||
println!(" ✓ Playlist vidée");
|
||||
println!(" Total tracks: {}", playlist.len().await);
|
||||
println!(" Is empty: {}", playlist.is_empty().await);
|
||||
println!(" Update ID final: {}\n", playlist.update_id().await);
|
||||
|
||||
println!("=== Exemple terminé ===");
|
||||
}
|
||||
217
old_code/pmoplaylist/examples/http_server_integration.rs
Normal file
217
old_code/pmoplaylist/examples/http_server_integration.rs
Normal file
@@ -0,0 +1,217 @@
|
||||
//! 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
|
||||
//! ```
|
||||
|
||||
use pmoplaylist::{FifoPlaylist, Track, DEFAULT_IMAGE};
|
||||
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() {
|
||||
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)),
|
||||
)
|
||||
.await;
|
||||
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",
|
||||
"http://media.server/music/stairway.flac",
|
||||
)
|
||||
.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);
|
||||
println!(
|
||||
" \"childCount\": {}",
|
||||
container.child_count.unwrap_or_default()
|
||||
);
|
||||
println!(" }}");
|
||||
}
|
||||
|
||||
/// Simule GET /playlist/items?offset=X&count=Y
|
||||
async fn simulate_get_items(playlist: Arc<FifoPlaylist>, offset: usize, count: usize) {
|
||||
let items = playlist
|
||||
.as_objects(offset, count, Some("http://media.server/api/default-image"))
|
||||
.await;
|
||||
|
||||
println!(" Response: {} items", items.len());
|
||||
println!(" [");
|
||||
for (idx, item) in items.iter().enumerate() {
|
||||
println!(" {{");
|
||||
println!(" \"id\": \"{}\",", item.id);
|
||||
println!(" \"title\": \"{}\",", item.title);
|
||||
println!(
|
||||
" \"artist\": \"{}\",",
|
||||
item.artist.as_deref().unwrap_or("")
|
||||
);
|
||||
println!(
|
||||
" \"album\": \"{}\",",
|
||||
item.album.as_deref().unwrap_or("")
|
||||
);
|
||||
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;
|
||||
|
||||
println!(
|
||||
" Track added: {} - {}",
|
||||
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());
|
||||
}
|
||||
198
old_code/pmoplaylist/examples/radio_streaming.rs
Normal file
198
old_code/pmoplaylist/examples/radio_streaming.rs
Normal file
@@ -0,0 +1,198 @@
|
||||
//! Exemple simulant une radio en streaming
|
||||
//!
|
||||
//! Cet exemple démontre :
|
||||
//! - L'utilisation de FifoPlaylist dans un contexte multi-thread
|
||||
//! - La simulation d'un flux radio continu
|
||||
//! - La surveillance des changements via update_id
|
||||
//!
|
||||
//! Pour exécuter :
|
||||
//! ```bash
|
||||
//! cargo run -p pmoplaylist --example radio_streaming
|
||||
//! ```
|
||||
|
||||
use pmoplaylist::{FifoPlaylist, Track, DEFAULT_IMAGE};
|
||||
use std::time::Duration;
|
||||
use tokio::time::sleep;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
println!("=== Simulation Radio en Streaming ===\n");
|
||||
|
||||
// Créer une radio avec historique limité à 10 tracks
|
||||
let radio = FifoPlaylist::new(
|
||||
"radio-paradise".to_string(),
|
||||
"Radio Paradise - Main Mix".to_string(),
|
||||
10,
|
||||
DEFAULT_IMAGE,
|
||||
);
|
||||
|
||||
println!("📻 Radio créée: {}", radio.title().await);
|
||||
println!("📊 Capacité: 10 tracks (historique limité)");
|
||||
println!("🆔 ID: {}\n", radio.id().await);
|
||||
|
||||
// Cloner pour les différentes tâches
|
||||
let radio_streamer = radio.clone();
|
||||
let radio_monitor = radio.clone();
|
||||
let radio_client = radio.clone();
|
||||
|
||||
// Tâche 1: Simuler le streaming (ajoute des tracks régulièrement)
|
||||
let streamer = tokio::spawn(async move {
|
||||
println!("🎵 [STREAMER] Démarrage du flux radio...\n");
|
||||
|
||||
let tracks_data = vec![
|
||||
("Radiohead", "Paranoid Android", "OK Computer", 383),
|
||||
("Massive Attack", "Teardrop", "Mezzanine", 329),
|
||||
(
|
||||
"Pink Floyd",
|
||||
"Shine On You Crazy Diamond",
|
||||
"Wish You Were Here",
|
||||
810,
|
||||
),
|
||||
("Portishead", "Glory Box", "Dummy", 305),
|
||||
("Dire Straits", "Sultans of Swing", "Dire Straits", 349),
|
||||
("The Cure", "Pictures of You", "Disintegration", 428),
|
||||
("David Bowie", "Heroes", "Heroes", 371),
|
||||
(
|
||||
"Talking Heads",
|
||||
"Once in a Lifetime",
|
||||
"Remain in Light",
|
||||
259,
|
||||
),
|
||||
("Fleetwood Mac", "Dreams", "Rumours", 257),
|
||||
(
|
||||
"The Smiths",
|
||||
"There Is a Light That Never Goes Out",
|
||||
"The Queen Is Dead",
|
||||
244,
|
||||
),
|
||||
("Joy Division", "Love Will Tear Us Apart", "Closer", 206),
|
||||
("New Order", "Blue Monday", "Power, Corruption & Lies", 448),
|
||||
("Depeche Mode", "Enjoy the Silence", "Violator", 376),
|
||||
("R.E.M.", "Losing My Religion", "Out of Time", 269),
|
||||
(
|
||||
"U2",
|
||||
"Where the Streets Have No Name",
|
||||
"The Joshua Tree",
|
||||
337,
|
||||
),
|
||||
];
|
||||
|
||||
for (idx, (artist, title, album, duration)) in tracks_data.iter().enumerate() {
|
||||
let track = Track::new(
|
||||
format!("radio-track-{}", idx),
|
||||
*title,
|
||||
format!("http://stream.radioparadise.com/track/{}", idx),
|
||||
)
|
||||
.with_artist(*artist)
|
||||
.with_album(*album)
|
||||
.with_duration(*duration);
|
||||
|
||||
radio_streamer.append_track(track).await;
|
||||
|
||||
println!("🎵 [STREAMER] Now Playing: {} - {}", artist, title);
|
||||
|
||||
// Simuler l'attente entre les tracks
|
||||
sleep(Duration::from_millis(500)).await;
|
||||
}
|
||||
|
||||
println!("\n🎵 [STREAMER] Fin du streaming");
|
||||
});
|
||||
|
||||
// Tâche 2: Monitorer les changements (update_id)
|
||||
let monitor = tokio::spawn(async move {
|
||||
sleep(Duration::from_millis(100)).await;
|
||||
|
||||
println!("👁️ [MONITOR] Surveillance des changements...\n");
|
||||
|
||||
let mut last_update_id = 0;
|
||||
let mut iterations = 0;
|
||||
|
||||
loop {
|
||||
let current_update_id = radio_monitor.update_id().await;
|
||||
let count = radio_monitor.len().await;
|
||||
|
||||
if current_update_id != last_update_id {
|
||||
println!(
|
||||
"👁️ [MONITOR] Changement détecté! Update ID: {} → {} | Tracks: {}",
|
||||
last_update_id, current_update_id, count
|
||||
);
|
||||
last_update_id = current_update_id;
|
||||
}
|
||||
|
||||
iterations += 1;
|
||||
if iterations >= 50 {
|
||||
break;
|
||||
}
|
||||
|
||||
sleep(Duration::from_millis(200)).await;
|
||||
}
|
||||
|
||||
println!("\n👁️ [MONITOR] Fin de la surveillance");
|
||||
});
|
||||
|
||||
// Tâche 3: Client consultant l'historique
|
||||
let client = tokio::spawn(async move {
|
||||
sleep(Duration::from_millis(2000)).await;
|
||||
|
||||
println!("\n📱 [CLIENT] Consultation de l'historique de la radio...\n");
|
||||
|
||||
// Consulter plusieurs fois pendant le streaming
|
||||
for i in 0..3 {
|
||||
sleep(Duration::from_millis(2000)).await;
|
||||
|
||||
let history = radio_client.get_items(0, 10).await;
|
||||
let update_id = radio_client.update_id().await;
|
||||
|
||||
println!(
|
||||
"📱 [CLIENT] Consultation #{} (Update ID: {})",
|
||||
i + 1,
|
||||
update_id
|
||||
);
|
||||
println!(" Historique actuel ({} tracks):", history.len());
|
||||
|
||||
for (idx, track) in history.iter().enumerate() {
|
||||
let artist = track.artist.as_deref().unwrap_or("Unknown");
|
||||
println!(" {}. {} - {}", idx + 1, artist, track.title);
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
||||
// Générer le container DIDL-Lite à la fin
|
||||
println!("📱 [CLIENT] Génération du Container DIDL-Lite...");
|
||||
let container = radio_client.as_container().await;
|
||||
println!(" Container ID: {}", container.id);
|
||||
println!(" Title: {}", container.title);
|
||||
println!(
|
||||
" Child Count: {}",
|
||||
container.child_count.unwrap_or_default()
|
||||
);
|
||||
|
||||
println!("\n📱 [CLIENT] Fin de la consultation");
|
||||
});
|
||||
|
||||
// Attendre que toutes les tâches se terminent
|
||||
let _ = tokio::join!(streamer, monitor, client);
|
||||
|
||||
// Afficher l'état final
|
||||
println!("\n=== État Final ===");
|
||||
println!("📊 Total tracks dans la radio: {}", radio.len().await);
|
||||
println!("🆔 Update ID final: {}", radio.update_id().await);
|
||||
|
||||
let final_history = radio.get_items(0, 10).await;
|
||||
println!("\n🎵 Historique final (10 derniers tracks):");
|
||||
for (idx, track) in final_history.iter().enumerate() {
|
||||
let artist = track.artist.as_deref().unwrap_or("Unknown");
|
||||
let duration_min = track.duration.map(|d| d / 60).unwrap_or(0);
|
||||
let duration_sec = track.duration.map(|d| d % 60).unwrap_or(0);
|
||||
println!(
|
||||
" {}. {} - {} ({}:{:02})",
|
||||
idx + 1,
|
||||
artist,
|
||||
track.title,
|
||||
duration_min,
|
||||
duration_sec
|
||||
);
|
||||
}
|
||||
|
||||
println!("\n=== Simulation terminée ===");
|
||||
}
|
||||
Reference in New Issue
Block a user