//! Simple Chromecast playback test //! //! This example tests basic Chromecast functionality by: //! 1. Connecting to a Chromecast device //! 2. Launching the DefaultMediaReceiver app //! 3. Loading and playing a test media URL //! 4. Maintaining the heartbeat loop //! //! Usage: //! cargo run --example chromecast_playback_test -- [media_url] //! //! Example: //! cargo run --example chromecast_playback_test -- 192.168.1.100 use std::env; use std::sync::Once; use rust_cast::{ CastDevice, ChannelMessage, channels::{ heartbeat::HeartbeatResponse, media::{Media, StreamType}, receiver::CastDeviceApp, }, }; const DEFAULT_DESTINATION_ID: &str = "receiver-0"; const DEFAULT_PORT: u16 = 8009; // Test media URLs (public domain audio files) const TEST_MEDIA_URL: &str = "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3"; /// Ensures the Rustls CryptoProvider is initialized exactly once. fn ensure_crypto_provider_initialized() { static INIT: Once = Once::new(); INIT.call_once(|| { let _ = rustls::crypto::CryptoProvider::install_default( rustls::crypto::aws_lc_rs::default_provider(), ); println!("✓ Rustls CryptoProvider initialized"); }); } /// Detects content type from URL path fn detect_content_type(url: &str) -> String { // Detect from URL path - check if path contains /flac/, /mp3/, etc. if url.contains("/flac/") || url.contains(".flac") { println!(" ✓ Detected FLAC from URL path"); return "audio/flac".to_string(); } if url.contains("/mp3/") || url.contains(".mp3") { println!(" ✓ Detected MP3 from URL path"); return "audio/mpeg".to_string(); } if url.contains(".m4a") || url.contains(".mp4") || url.contains(".aac") { println!(" ✓ Detected AAC/M4A from URL path"); return "audio/mp4".to_string(); } if url.contains("/ogg/") || url.contains(".ogg") { println!(" ✓ Detected OGG from URL path"); return "audio/ogg".to_string(); } if url.contains(".opus") { println!(" ✓ Detected Opus from URL path"); return "audio/opus".to_string(); } if url.contains(".wav") { println!(" ✓ Detected WAV from URL path"); return "audio/wav".to_string(); } // Fallback println!(" ⚠ Could not detect type, using audio/mpeg as fallback"); "audio/mpeg".to_string() } fn main() { // Setup logging tracing_subscriber::fmt() .with_max_level(tracing::Level::DEBUG) .init(); // Parse command line arguments let args: Vec = env::args().collect(); if args.len() < 2 { eprintln!("Usage: {} [media_url]", args[0]); eprintln!("\nExample:"); eprintln!(" {} 192.168.1.100", args[0]); eprintln!( "\nIf no media URL is provided, will use: {}", TEST_MEDIA_URL ); std::process::exit(1); } let chromecast_ip = &args[1]; let media_url = if args.len() > 2 { &args[2] } else { TEST_MEDIA_URL }; println!("╔════════════════════════════════════════════════════════════╗"); println!("║ Chromecast Playback Test (rust_cast) ║"); println!("╚════════════════════════════════════════════════════════════╝"); println!(); println!("Target: {}:{}", chromecast_ip, DEFAULT_PORT); println!("Media URL: {}", media_url); println!(); println!("Detecting media content type..."); let media_type = detect_content_type(media_url); println!(); // Initialize crypto provider ensure_crypto_provider_initialized(); // Step 1: Connect to the device println!("──────────────────────────────────────────────────────────"); println!("STEP 1: Connecting to Chromecast..."); let cast_device = match CastDevice::connect_without_host_verification(chromecast_ip, DEFAULT_PORT) { Ok(device) => { println!("✓ Connected to Chromecast"); device } Err(e) => { eprintln!("✗ Failed to connect: {}", e); std::process::exit(1); } }; // Step 2: Connect to the default receiver channel println!(); println!("STEP 2: Connecting to receiver channel..."); if let Err(e) = cast_device .connection .connect(DEFAULT_DESTINATION_ID.to_string()) { eprintln!("✗ Failed to connect channel: {}", e); std::process::exit(1); } println!("✓ Channel connected"); // Step 3: Send initial ping (CRITICAL per rust_caster.rs) println!(); println!("STEP 3: Sending initial heartbeat ping..."); if let Err(e) = cast_device.heartbeat.ping() { eprintln!("✗ Failed to send initial ping: {}", e); std::process::exit(1); } println!("✓ Initial ping sent"); // Step 4: Get receiver status println!(); println!("STEP 4: Getting receiver status..."); let status = match cast_device.receiver.get_status() { Ok(status) => { println!("✓ Receiver status obtained"); println!( " - Volume: {:.0}%", status.volume.level.unwrap_or(0.5) * 100.0 ); println!(" - Muted: {}", status.volume.muted.unwrap_or(false)); println!(" - Running apps: {}", status.applications.len()); status } Err(e) => { eprintln!("✗ Failed to get status: {}", e); std::process::exit(1); } }; // Step 5: Launch DefaultMediaReceiver println!(); println!("STEP 5: Launching DefaultMediaReceiver app..."); let app = match cast_device .receiver .launch_app(&CastDeviceApp::DefaultMediaReceiver) { Ok(app) => { println!("✓ App launched successfully"); println!(" - App ID: {}", app.app_id); println!(" - Display Name: {}", app.display_name); println!(" - Session ID: {}", app.session_id); println!(" - Transport ID: {}", app.transport_id); app } Err(e) => { eprintln!("✗ Failed to launch app: {}", e); std::process::exit(1); } }; // Step 6: Connect to the app's transport println!(); println!("STEP 6: Connecting to app transport..."); if let Err(e) = cast_device.connection.connect(app.transport_id.as_str()) { eprintln!("✗ Failed to connect to app transport: {}", e); std::process::exit(1); } println!("✓ Connected to app transport: {}", app.transport_id); // Step 7: Load the media println!(); println!("STEP 7: Loading media..."); println!(" Content-Type: {}", media_type); let media = Media { content_id: media_url.to_string(), content_type: media_type, stream_type: StreamType::Buffered, duration: None, metadata: None, }; match cast_device .media .load(app.transport_id.as_str(), app.session_id.as_str(), &media) { Ok(status) => { println!("✓ Media loaded successfully!"); println!(" - Media status entries: {}", status.entries.len()); if let Some(entry) = status.entries.first() { println!(" - Player state: {:?}", entry.player_state); println!(" - Media session ID: {}", entry.media_session_id); if let Some(ref media) = entry.media { println!(" - Content ID: {}", media.content_id); println!(" - Stream type: {:?}", media.stream_type); } } } Err(e) => { eprintln!("✗ Failed to load media: {}", e); std::process::exit(1); } } // Step 8: Enter heartbeat loop println!(); println!("──────────────────────────────────────────────────────────"); println!("STEP 8: Entering heartbeat loop (Ctrl+C to exit)..."); println!("──────────────────────────────────────────────────────────"); println!(); let mut heartbeat_count = 0; let mut media_message_count = 0; loop { match cast_device.receive() { Ok(ChannelMessage::Heartbeat(response)) => { if let HeartbeatResponse::Ping = response { heartbeat_count += 1; println!( "[Heartbeat #{:3}] Received Ping, sending Pong...", heartbeat_count ); if let Err(e) = cast_device.heartbeat.pong() { eprintln!("✗ Failed to send pong: {}", e); break; } } else { println!("[Heartbeat] {:?}", response); } } Ok(ChannelMessage::Media(response)) => { media_message_count += 1; println!("[Media #{:3}] {:?}", media_message_count, response); } Ok(ChannelMessage::Receiver(response)) => { println!("[Receiver] {:?}", response); } Ok(ChannelMessage::Connection(response)) => { println!("[Connection] {:?}", response); } Ok(ChannelMessage::Raw(response)) => { println!("[Raw] Unsupported message type: {:?}", response); } Err(e) => { eprintln!("✗ Error receiving message: {}", e); break; } } } println!(); println!("──────────────────────────────────────────────────────────"); println!("Test completed."); println!("Total heartbeats: {}", heartbeat_count); println!("Total media messages: {}", media_message_count); }