diff --git a/src/audio.rs b/src/audio.rs index b3525fe..fdd0e49 100644 --- a/src/audio.rs +++ b/src/audio.rs @@ -9,7 +9,7 @@ use symphonia::core::formats::{FormatOptions, FormatReader}; use symphonia::core::io::MediaSourceStream; use symphonia::core::meta::MetadataOptions; use symphonia::core::probe::Hint; -use tracing::{info, debug, error, warn}; +use tracing::{//info!, debug, error, warn}; pub struct AsioPlayer { device: Device, @@ -35,11 +35,11 @@ struct StreamBuffer { impl AsioPlayer { pub fn new() -> Result { let host = cpal::host_from_id(cpal::HostId::Asio)?; - let device = host.default_output_device() + let mut devices = host.output_devices()?; + let device = devices.next() .ok_or_else(|| anyhow::anyhow!("No ASIO output device found"))?; - - info!("Using ASIO device: {}", device.name()?); - + //info!!("Using ASIO device: {}", device.name()?); + Ok(Self { device, stream: None, @@ -75,17 +75,17 @@ impl AsioPlayer { } self.forced_sample_rate = Some(sample_rate); - info!("Forced sample rate set to {} Hz", sample_rate); + //info!!("Forced sample rate set to {} Hz", sample_rate); Ok(()) } pub fn clear_sample_rate(&mut self) { self.forced_sample_rate = None; - info!("Sample rate forcing cleared - will use automatic selection"); + ////info!!("Sample rate forcing cleared - will use automatic selection"); } pub async fn play_url(&mut self, url: &str) -> Result<()> { - info!("Starting playback of: {}", url); + ////info!!("Starting playback of: {}", url); // Stop any existing playback self.stop()?; @@ -104,7 +104,7 @@ impl AsioPlayer { stream.play()?; self.start_time = Some(Instant::now()); self.paused_position = None; - info!("Playback started"); + //info!!("Playback started"); } Ok(()) @@ -127,7 +127,7 @@ impl AsioPlayer { buffer.paused = true; } - info!("Playback paused"); + //info!!("Playback paused"); Ok(()) } else { Err(anyhow::anyhow!("No active stream to pause")) @@ -150,7 +150,7 @@ impl AsioPlayer { } stream.play()?; - info!("Playback resumed"); + //info!!("Playback resumed"); Ok(()) } else { Err(anyhow::anyhow!("No active stream to resume")) @@ -159,15 +159,26 @@ impl AsioPlayer { pub fn stop(&mut self) -> Result<()> { if let Some(stream) = &self.stream { + // First, clear the buffer to prevent noise + if let Some(buffer) = &self.stream_buffer { + let mut buffer = buffer.lock().unwrap(); + buffer.paused = true; // Stop audio immediately + buffer.position = 0; // Reset position + buffer.samples.clear(); // Clear the buffer completely + } + + std::thread::sleep(std::time::Duration::from_millis(50)); + stream.pause()?; + self.stream = None; self.stream_buffer = None; self.start_time = None; self.paused_position = None; - info!("Playback stopped"); + + ////info!!("Playback stopped and buffer cleared"); Ok(()) } else { - // Not an error if already stopped Ok(()) } } @@ -195,7 +206,7 @@ impl AsioPlayer { } } - info!("Seeked to position: {:.1}%", position * 100.0); + ////info!!("Seeked to position: {:.1}%", position * 100.0); Ok(()) } else { Err(anyhow::anyhow!("No active stream to seek")) @@ -209,7 +220,7 @@ impl AsioPlayer { if let Some(buffer) = &self.stream_buffer { let mut buffer = buffer.lock().unwrap(); buffer.volume = volume; - info!("Volume set to: {:.0}%", volume * 100.0); + ////info!!("Volume set to: {:.0}%", volume * 100.0); } Ok(()) @@ -265,7 +276,7 @@ impl AsioPlayer { let mut decoder = symphonia::default::get_codecs() .make(&track.codec_params, &decoder_opts)?; - info!("Decoding: {} channels, {} Hz", channels, sample_rate); + ////info!!!("Decoding: {} channels, {} Hz", channels, sample_rate); let mut samples = Vec::new(); @@ -355,7 +366,7 @@ impl AsioPlayer { let num_frames = samples.len() / channels as usize; let duration_ms = (num_frames as f64 / sample_rate as f64 * 1000.0) as u64; - info!("Decoded {} samples, duration: {}ms", samples.len(), duration_ms); + //info!!("Decoded {} samples, duration: {}ms", samples.len(), duration_ms); Ok((samples, sample_rate, channels, duration_ms)) } @@ -375,7 +386,7 @@ impl AsioPlayer { config.channels() == source_channels { selected_config = Some(config.with_sample_rate(SampleRate(forced_rate))); target_sample_rate = forced_rate; - info!("Using forced sample rate: {} Hz", forced_rate); + //info!!("Using forced sample rate: {} Hz", forced_rate); break; } } @@ -435,8 +446,8 @@ impl AsioPlayer { self.device.default_output_config()? }; - info!("Selected config: {} channels, {} Hz, format: {:?}", - config.channels(), config.sample_rate().0, config.sample_format()); + //info!!("Selected config: {} channels, {} Hz, format: {:?}", + // config.channels(), config.sample_rate().0, config.sample_format()); // Resample if necessary let final_samples = if target_sample_rate != source_sample_rate { @@ -510,7 +521,7 @@ impl AsioPlayer { fn build_f32_stream(&mut self, config: StreamConfig, audio_buffer: Arc>) -> Result<()> { let stream = self.device.build_output_stream( &config, - move |data: &mut [f32], _: &cpal::OutputCallbackInfo| { + move |data: &mut [f32], _: &cpal::OutputCallback//info!| { let mut buffer = audio_buffer.lock().unwrap(); // If paused, just output silence @@ -555,7 +566,7 @@ impl AsioPlayer { fn build_i32_stream(&mut self, config: StreamConfig, audio_buffer: Arc>) -> Result<()> { let stream = self.device.build_output_stream( &config, - move |data: &mut [i32], _: &cpal::OutputCallbackInfo| { + move |data: &mut [i32], _: &cpal::OutputCallback//info!| { let mut buffer = audio_buffer.lock().unwrap(); // If paused, just output silence @@ -590,7 +601,7 @@ impl AsioPlayer { fn build_i16_stream(&mut self, config: StreamConfig, audio_buffer: Arc>) -> Result<()> { let stream = self.device.build_output_stream( &config, - move |data: &mut [i16], _: &cpal::OutputCallbackInfo| { + move |data: &mut [i16], _: &cpal::OutputCallback//info!| { let mut buffer = audio_buffer.lock().unwrap(); // If paused, just output silence diff --git a/src/jellyfin.rs b/src/jellyfin.rs index 7e1a58f..8738cd8 100644 --- a/src/jellyfin.rs +++ b/src/jellyfin.rs @@ -130,15 +130,15 @@ impl JellyfinClient { pub fn get_stream_url(&self, item_id: &str) -> Result { let user_id = self.config.user_id.as_ref().ok_or_else(|| anyhow::anyhow!("Not authenticated"))?; - let token = self.config.access_token.as_ref().ok_or_else(|| anyhow::anyhow!("Not authenticated"))?; + let token = self.config.access_token.as_ref().ok_or_else(|| anyhow::anyhow!("Not authenticated"))?; + + let url = format!( + "{}/Audio/{}/stream?UserId={}&api_key={}&Static=true", + self.config.server_url, item_id, user_id, token + ); - let url = format!( - "{}/Audio/{}/stream?UserId={}&api_key={}&Static=true", - self.config.server_url, item_id, user_id, token - ); - - Ok(url) - } + Ok(url) +} pub fn is_authenticated(&self) -> bool { self.config.is_authenticated() diff --git a/src/main.rs b/src/main.rs index 3425ce2..6a49526 100644 --- a/src/main.rs +++ b/src/main.rs @@ -51,110 +51,10 @@ async fn main() -> Result<()> { 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(()) -} \ No newline at end of file diff --git a/src/tui.rs b/src/tui.rs index 4622fd3..d49a045 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -12,6 +12,7 @@ use std::io::{self, Stdout}; use std::sync::mpsc::{channel, Receiver, Sender}; use std::time::{Duration, Instant}; use std::thread; +use::tracing::{debug, info}; use crate::jellyfin::{JellyfinClient, LibraryItem}; use crate::player::{Player, PlayerCommand, PlaybackState}; @@ -312,7 +313,7 @@ impl Tui { // Player controls (only if not showing help and not in search mode) if !self.show_help && self.active_tab != TAB_SEARCH { match key.code { - KeyCode::Char(' ') => { + KeyCode::Char('o') => { self.player.send_command(PlayerCommand::PlayPause).await?; return Ok(false); }, @@ -448,9 +449,9 @@ impl Tui { if let Some(selected) = self.songs_state.selected() { if selected < self.songs.len() { let song = &self.songs[selected]; - let url = self.jellyfin.get_stream_url(&song.id)?; - self.player.send_command(PlayerCommand::Play(url)).await?; - + debug!("Song ID: {}", song.id); + self.player.send_command(PlayerCommand::Play(song.id.clone())).await?; + if !self.queue.iter().any(|item| item.id == song.id) { self.queue.push(song.clone()); } @@ -492,8 +493,8 @@ impl Tui { if let Some(selected) = self.queue_state.selected() { if selected < self.queue.len() { let song = &self.queue[selected]; - let url = self.jellyfin.get_stream_url(&song.id)?; - self.player.send_command(PlayerCommand::Play(url)).await?; + self.player.send_command(PlayerCommand::Play(song.id.clone())).await?; + } } } @@ -543,16 +544,14 @@ impl Tui { } } KeyCode::Enter => { - if let Some(selected) = self.search_state.selected() { - if selected < self.search_results.len() { - let song = &self.search_results[selected]; - if song.item_type == "Audio" { - let url = self.jellyfin.get_stream_url(&song.id)?; - self.player.send_command(PlayerCommand::Play(url)).await?; - - if !self.queue.iter().any(|item| item.id == song.id) { - self.queue.push(song.clone()); - } + if let Some(selected) = self.songs_state.selected() { + if selected < self.songs.len() { + let song = &self.songs[selected]; + debug!("Song ID: {}", song.id); + self.player.send_command(PlayerCommand::Play(song.id.clone())).await?; + + if !self.queue.iter().any(|item| item.id == song.id) { + self.queue.push(song.clone()); } } } @@ -860,7 +859,7 @@ impl Tui { f.render_widget(playing_text, chunks[0]); // Controls help - let controls = "Space: Play/Pause | s: Stop | n/p: Next/Prev"; + let controls = "o: Play/Pause | s: Stop | n/p: Next/Prev"; let controls_text = Paragraph::new(controls) .block(Block::default().borders(Borders::ALL).title("Controls")); f.render_widget(controls_text, chunks[1]);