use anyhow::Result; use tracing::info; use cpal::traits::{HostTrait, DeviceTrait}; use std::io::Write; mod jellyfin; mod audio; mod config; mod player; mod tui; #[tokio::main] async fn main() -> Result<()> { tracing_subscriber::fmt::init(); info!("Starting Jellyfin ASIO Client"); // Load config let mut config = config::Config::load()?; // Create Jellyfin client let mut jellyfin = jellyfin::JellyfinClient::new(config.clone()); // Check if we need to authenticate if !jellyfin.is_authenticated() { println!("No saved session found. Please log in."); // Get server URL if not set if config.server_url.is_empty() { print!("Enter Jellyfin server URL (e.g., https://jellyfin.example.com): "); std::io::stdout().flush()?; let mut url = String::new(); std::io::stdin().read_line(&mut url)?; config.server_url = url.trim().to_string(); config.save()?; jellyfin = jellyfin::JellyfinClient::new(config.clone()); } print!("Enter your Jellyfin username: "); std::io::stdout().flush()?; let mut username = String::new(); std::io::stdin().read_line(&mut username)?; username = username.trim().to_string(); print!("Enter your Jellyfin password: "); std::io::stdout().flush()?; let password = rpassword::read_password()?; jellyfin.authenticate(&username, &password).await? } else { info!("Using saved session for {}", config.username.unwrap_or_else(|| "Unknown user".to_string())); } // List ASIO devices list_asio_devices()?; // Create player let player = player::Player::new(jellyfin.clone())?; // Create and run TUI let mut tui = tui::Tui::new(jellyfin, player)?; tui.run().await?; Ok(()) } async fn test_jellyfin_connection() -> Result<()> { info!("Testing Jellyfin connection..."); let config = config::Config { server_url: "https://jfin.mercurio.moe".to_string(), ..config::Config::default() }; let mut client = jellyfin::JellyfinClient::new(config); println!("Enter your Jellyfin username:"); let mut username = String::new(); std::io::stdin().read_line(&mut username)?; username = username.trim().to_string(); println!("Enter your Jellyfin password:"); let password = rpassword::read_password()?; client.authenticate(&username, &password).await?; let items = client.get_music_library().await?; info!("Your music library (first few items):"); for item in items.iter().take(10) { let artist = item.artists.as_ref() .and_then(|a| a.first()) .map(|s| s.as_str()) .unwrap_or("Unknown Artist"); let album = item.album.as_ref() .map(|s| s.as_str()) .unwrap_or("Unknown Album"); info!(" {} - {} - {}", artist, album, item.name); if items.iter().position(|i| i.id == item.id) == Some(0) { let stream_url = client.get_stream_url(&item.id)?; info!(" Stream URL: {}", stream_url); println!("\nDo you want to play this track? (y/n):"); let mut input = String::new(); std::io::stdin().read_line(&mut input)?; if input.trim().to_lowercase() == "y" { let mut player = audio::AsioPlayer::new()?; println!("Do you want to set a specific sample rate? (y/n):"); let mut rate_input = String::new(); std::io::stdin().read_line(&mut rate_input)?; if rate_input.trim().to_lowercase() == "y" { println!("Enter sample rate (e.g., 44100, 48000, 96000, 192000):"); let mut rate_str = String::new(); std::io::stdin().read_line(&mut rate_str)?; if let Ok(sample_rate) = rate_str.trim().parse::() { match player.set_sample_rate(sample_rate) { Ok(_) => println!("Sample rate set to {} Hz", sample_rate), Err(e) => println!("Failed to set sample rate: {}", e), } } else { println!("Invalid sample rate, using automatic selection"); } } player.play_url(&stream_url).await?; } } } Ok(()) } fn list_asio_devices() -> Result<()> { info!("Enumerating ASIO devices..."); let host = cpal::host_from_id(cpal::HostId::Asio)?; let devices = host.output_devices()?; for (i, device) in devices.enumerate() { let name = device.name()?; info!("ASIO Device {}: {}", i, name); let supported_configs = device.supported_output_configs()?; for config in supported_configs { info!(" Channels: {}, Sample Rate: {}-{} Hz, Format: {:?}", config.channels(), config.min_sample_rate().0, config.max_sample_rate().0, config.sample_format()); } } Ok(()) }