Commit 99b5dd9

mo khan <mo@mokhan.ca>
2025-07-03 16:24:05
chore: format code with rustfmt
- Apply consistent code formatting across all source files - No functional changes, only style improvements 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 3099595
src/app.rs
@@ -1,9 +1,5 @@
+use crate::{config::Config, feed::Feed, player::Player};
 use anyhow::Result;
-use crate::{
-    config::Config,
-    feed::Feed,
-    player::Player,
-};
 use std::sync::mpsc;
 use std::thread;
 
@@ -31,33 +27,34 @@ pub struct CurrentTrack {
     pub title: String,
     pub podcast_name: String,
     pub _duration: Option<String>,
-    pub _position: f64,  // seconds
+    pub _position: f64, // seconds
 }
 
 impl App {
     pub fn new() -> Result<Self> {
         let config = Config::load()?;
-        let feeds: Vec<Feed> = config.feeds
+        let feeds: Vec<Feed> = config
+            .feeds
             .iter()
             .map(|(name, url)| Feed::new(name.clone(), url.clone()))
             .collect();
-        
+
         // Set up async feed loading
         let (sender, receiver) = mpsc::channel();
-        
+
         // Start background feed loading
         for (index, (name, url)) in config.feeds.iter().enumerate() {
             let sender = sender.clone();
             let name = name.clone();
             let url = url.clone();
-            
+
             thread::spawn(move || {
                 let mut feed = Feed::new(name, url);
                 let _ = feed.fetch_episodes(); // Ignore errors for now, state tracks them
                 let _ = sender.send((index, feed));
             });
         }
-        
+
         Ok(Self {
             current_screen: CurrentScreen::FeedList,
             feeds,
@@ -66,7 +63,7 @@ impl App {
             _config: config,
             player: Player::new()?,
             current_track: None,
-            volume: 70,  // Default volume 70%
+            volume: 70, // Default volume 70%
             feed_receiver: receiver,
         })
     }
@@ -140,10 +137,10 @@ impl App {
                     // Download and cache if not cached
                     self.player.play_with_cache(
                         &episode.enclosure_url,
-                        Some((&feed.name, &episode.title))
+                        Some((&feed.name, &episode.title)),
                     )?;
                 }
-                
+
                 // Set current track info and switch to NowPlaying screen
                 self.current_track = Some(CurrentTrack {
                     title: episode.title.clone(),
@@ -181,19 +178,19 @@ impl App {
     }
 
     pub fn seek_forward(&mut self) -> Result<()> {
-        self.player.skip_forward(15.0)  // 15 seconds forward (common podcast increment)
+        self.player.skip_forward(15.0) // 15 seconds forward (common podcast increment)
     }
 
     pub fn seek_backward(&mut self) -> Result<()> {
-        self.player.skip_backward(15.0)  // 15 seconds backward
+        self.player.skip_backward(15.0) // 15 seconds backward
     }
 
     pub fn skip_forward_long(&mut self) -> Result<()> {
-        self.player.skip_forward(60.0)  // 1 minute forward
+        self.player.skip_forward(60.0) // 1 minute forward
     }
 
     pub fn skip_backward_long(&mut self) -> Result<()> {
-        self.player.skip_backward(60.0)  // 1 minute backward
+        self.player.skip_backward(60.0) // 1 minute backward
     }
 
     pub fn stop_playback(&mut self) {
@@ -215,5 +212,4 @@ impl App {
             }
         }
     }
-
-}
\ No newline at end of file
+}
src/config.rs
@@ -14,7 +14,7 @@ pub struct Config {
 impl Config {
     pub fn load() -> Result<Self> {
         let config_path = Self::config_path()?;
-        
+
         if !config_path.exists() {
             // Create a default config
             let default_config = Self::default();
@@ -24,10 +24,13 @@ impl Config {
 
         let content = fs::read_to_string(&config_path)
             .with_context(|| format!("Failed to read config file: {:?}", config_path))?;
-        
+
         eprintln!("Loading config from: {:?}", config_path);
-        eprintln!("Config content preview: {}", &content[0..content.len().min(200)]);
-        
+        eprintln!(
+            "Config content preview: {}",
+            &content[0..content.len().min(200)]
+        );
+
         let config: Config = serde_yaml::from_str(&content)
             .with_context(|| format!("Failed to parse config file: {:?}", config_path))?;
 
@@ -41,14 +44,13 @@ impl Config {
 
     pub fn save(&self) -> Result<()> {
         let config_path = Self::config_path()?;
-        
+
         if let Some(parent) = config_path.parent() {
             fs::create_dir_all(parent)
                 .with_context(|| format!("Failed to create config directory: {:?}", parent))?;
         }
 
-        let content = serde_yaml::to_string(self)
-            .with_context(|| "Failed to serialize config")?;
+        let content = serde_yaml::to_string(self).with_context(|| "Failed to serialize config")?;
 
         fs::write(&config_path, content)
             .with_context(|| format!("Failed to write config file: {:?}", config_path))?;
@@ -64,7 +66,7 @@ impl Config {
                 return Ok(path);
             }
         }
-        
+
         // Then check ~/.config/ghetto-blaster.yml (Unix standard)
         if let Some(home_dir) = dirs::home_dir() {
             let path = home_dir.join(".config").join("ghetto-blaster.yml");
@@ -72,11 +74,10 @@ impl Config {
                 return Ok(path);
             }
         }
-        
+
         // Fall back to platform-specific config directory
-        let config_dir = dirs::config_dir()
-            .with_context(|| "Failed to find config directory")?;
-        
+        let config_dir = dirs::config_dir().with_context(|| "Failed to find config directory")?;
+
         Ok(config_dir.join("ghetto-blaster.yml"))
     }
 }
@@ -106,9 +107,7 @@ impl Default for Config {
         Self {
             feeds,
             radio: Some(radio),
-            music_dirs: Some(vec![
-                "~/Music".to_string(),
-            ]),
+            music_dirs: Some(vec!["~/Music".to_string()]),
         }
     }
-}
\ No newline at end of file
+}
src/feed.rs
@@ -72,7 +72,7 @@ impl Feed {
 
     pub fn fetch_episodes(&mut self) -> Result<()> {
         self.state = FeedState::Loading;
-        
+
         match self.try_fetch_episodes() {
             Ok(()) => {
                 self.state = FeedState::Loaded;
@@ -89,19 +89,22 @@ impl Feed {
         let response = reqwest::blocking::get(&self.url)
             .with_context(|| format!("Failed to fetch feed: {}", self.url))?;
 
-        let content = response.text()
+        let content = response
+            .text()
             .with_context(|| "Failed to read response body")?;
 
-        let rss: Rss = from_str(&content)
-            .with_context(|| "Failed to parse RSS feed")?;
+        let rss: Rss = from_str(&content).with_context(|| "Failed to parse RSS feed")?;
 
-        self.episodes = rss.channel.items
+        self.episodes = rss
+            .channel
+            .items
             .into_iter()
             .filter_map(|item| self.parse_episode(item))
             .collect();
 
         // Sort episodes by date (newest first)
-        self.episodes.sort_by(|a, b| b.published_at.cmp(&a.published_at));
+        self.episodes
+            .sort_by(|a, b| b.published_at.cmp(&a.published_at));
 
         // Scan for cached episodes and merge them
         self.scan_and_merge_cached_episodes();
@@ -112,12 +115,13 @@ impl Feed {
     fn scan_and_merge_cached_episodes(&mut self) {
         // First, mark RSS episodes that are cached
         self.mark_cached_episodes();
-        
+
         // Then, add cache-only episodes that aren't in RSS
         self.add_cached_only_episodes();
-        
+
         // Sort again after merging
-        self.episodes.sort_by(|a, b| b.published_at.cmp(&a.published_at));
+        self.episodes
+            .sort_by(|a, b| b.published_at.cmp(&a.published_at));
     }
 
     fn mark_cached_episodes(&mut self) {
@@ -127,7 +131,9 @@ impl Feed {
         }
 
         // Collect cache paths first to avoid borrowing issues
-        let cache_paths: Vec<_> = self.episodes.iter()
+        let cache_paths: Vec<_> = self
+            .episodes
+            .iter()
             .map(|ep| self.get_cache_path_for_url(&ep.enclosure_url))
             .collect();
 
@@ -152,7 +158,10 @@ impl Feed {
                     // Check if this cached file is already represented in RSS episodes
                     let filename = path.file_name().unwrap().to_string_lossy();
                     let already_exists = self.episodes.iter().any(|ep| {
-                        ep.local_path.as_ref().map(|p| p.contains(&*filename)).unwrap_or(false)
+                        ep.local_path
+                            .as_ref()
+                            .map(|p| p.contains(&*filename))
+                            .unwrap_or(false)
                     });
 
                     if !already_exists {
@@ -175,8 +184,15 @@ impl Feed {
         let cache_dir = self.get_cache_directory();
         let filename = url.split('/').last().unwrap_or("episode.mp3");
         let filename = filename.split('?').next().unwrap_or(filename);
-        let safe_filename = filename.chars()
-            .map(|c| if c.is_alphanumeric() || c == '.' || c == '-' { c } else { '_' })
+        let safe_filename = filename
+            .chars()
+            .map(|c| {
+                if c.is_alphanumeric() || c == '.' || c == '-' {
+                    c
+                } else {
+                    '_'
+                }
+            })
             .collect::<String>();
         cache_dir.join(safe_filename)
     }
@@ -193,8 +209,9 @@ impl Feed {
     fn create_cached_episode(&self, path: &PathBuf) -> Option<Episode> {
         let filename = path.file_stem()?.to_string_lossy();
         let title = filename.replace('_', " ").replace('-', " ");
-        
-        let published_at = path.metadata()
+
+        let published_at = path
+            .metadata()
             .ok()
             .and_then(|m| m.modified().ok())
             .and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
@@ -215,8 +232,9 @@ impl Feed {
     fn parse_episode(&self, item: RssItem) -> Option<Episode> {
         let title = item.title?;
         let enclosure_url = item.enclosure?.url;
-        
-        let published_at = item.pub_date
+
+        let published_at = item
+            .pub_date
             .and_then(|date_str| {
                 // Try parsing RFC 2822 format first
                 DateTime::parse_from_rfc2822(&date_str)
@@ -235,4 +253,4 @@ impl Feed {
             local_path: None,
         })
     }
-}
\ No newline at end of file
+}
src/main.rs
@@ -34,13 +34,13 @@ fn main() -> Result<()> {
 
     // Try different approach for macOS compatibility
     let mut stdout = io::stdout();
-    
+
     // Set up panic handler to cleanup terminal
     std::panic::set_hook(Box::new(|_| {
         let _ = disable_raw_mode();
         let _ = execute!(io::stdout(), LeaveAlternateScreen, DisableMouseCapture);
     }));
-    
+
     // Try to enable raw mode with a fallback
     let raw_mode_enabled = match enable_raw_mode() {
         Ok(()) => true,
@@ -56,7 +56,7 @@ fn main() -> Result<()> {
             false
         }
     };
-    
+
     if !raw_mode_enabled {
         // Simple mode - just show the config and exit
         println!("\n=== GHETTO-BLASTER CONFIG TEST ===");
@@ -69,12 +69,11 @@ fn main() -> Result<()> {
         println!("\nTo use the full TUI interface, run this app in a proper terminal.");
         return Ok(());
     }
-    
+
     execute!(stdout, EnterAlternateScreen, EnableMouseCapture)
         .with_context(|| "Failed to setup terminal screen")?;
     let backend = CrosstermBackend::new(stdout);
-    let mut terminal = Terminal::new(backend)
-        .with_context(|| "Failed to create terminal")?;
+    let mut terminal = Terminal::new(backend).with_context(|| "Failed to create terminal")?;
 
     // Main loop
     let res = run_app(&mut terminal, &mut app);
@@ -99,45 +98,45 @@ fn run_app<B: Backend>(terminal: &mut Terminal<B>, app: &mut App) -> Result<()>
     loop {
         // Update feeds from background loading
         app.update_feeds();
-        
+
         terminal.draw(|f| ui(f, app))?;
 
         // Use poll instead of read to avoid blocking, allowing for regular UI updates
         if event::poll(std::time::Duration::from_millis(100))? {
             if let Event::Key(key) = event::read()? {
                 match app.current_screen {
-                CurrentScreen::FeedList => match key.code {
-                    KeyCode::Char('q') => return Ok(()),
-                    KeyCode::Char('j') | KeyCode::Down => app.next_feed(),
-                    KeyCode::Char('k') | KeyCode::Up => app.previous_feed(),
-                    KeyCode::Enter => app.select_feed(),
-                    KeyCode::Char('r') => app.refresh_feeds()?,
-                    _ => {}
-                },
-                CurrentScreen::EpisodeList => match key.code {
-                    KeyCode::Char('q') => return Ok(()),
-                    KeyCode::Char('h') | KeyCode::Left | KeyCode::Esc => app.back_to_feeds(),
-                    KeyCode::Char('j') | KeyCode::Down => app.next_episode(),
-                    KeyCode::Char('k') | KeyCode::Up => app.previous_episode(),
-                    KeyCode::Enter | KeyCode::Char(' ') => app.play_episode()?,
-                    _ => {}
-                },
-                CurrentScreen::NowPlaying => match key.code {
-                    KeyCode::Char('q') => return Ok(()),
-                    KeyCode::Char('h') | KeyCode::Left | KeyCode::Esc => app.back_to_episodes(),
-                    KeyCode::Char(' ') => app.toggle_playback()?,
-                    KeyCode::Char('s') => app.stop_playback(),
-                    KeyCode::Char('+') | KeyCode::Char('=') => app.volume_up()?,
-                    KeyCode::Char('-') => app.volume_down()?,
-                    // 15-second seeking
-                    KeyCode::Char('l') | KeyCode::Right => app.seek_forward()?,
-                    KeyCode::Char('j') | KeyCode::Down => app.seek_backward()?,
-                    // 1-minute seeking
-                    KeyCode::Char('L') => app.skip_forward_long()?,
-                    KeyCode::Char('J') => app.skip_backward_long()?,
-                    _ => {}
-                },
-            }
+                    CurrentScreen::FeedList => match key.code {
+                        KeyCode::Char('q') => return Ok(()),
+                        KeyCode::Char('j') | KeyCode::Down => app.next_feed(),
+                        KeyCode::Char('k') | KeyCode::Up => app.previous_feed(),
+                        KeyCode::Enter => app.select_feed(),
+                        KeyCode::Char('r') => app.refresh_feeds()?,
+                        _ => {}
+                    },
+                    CurrentScreen::EpisodeList => match key.code {
+                        KeyCode::Char('q') => return Ok(()),
+                        KeyCode::Char('h') | KeyCode::Left | KeyCode::Esc => app.back_to_feeds(),
+                        KeyCode::Char('j') | KeyCode::Down => app.next_episode(),
+                        KeyCode::Char('k') | KeyCode::Up => app.previous_episode(),
+                        KeyCode::Enter | KeyCode::Char(' ') => app.play_episode()?,
+                        _ => {}
+                    },
+                    CurrentScreen::NowPlaying => match key.code {
+                        KeyCode::Char('q') => return Ok(()),
+                        KeyCode::Char('h') | KeyCode::Left | KeyCode::Esc => app.back_to_episodes(),
+                        KeyCode::Char(' ') => app.toggle_playback()?,
+                        KeyCode::Char('s') => app.stop_playback(),
+                        KeyCode::Char('+') | KeyCode::Char('=') => app.volume_up()?,
+                        KeyCode::Char('-') => app.volume_down()?,
+                        // 15-second seeking
+                        KeyCode::Char('l') | KeyCode::Right => app.seek_forward()?,
+                        KeyCode::Char('j') | KeyCode::Down => app.seek_backward()?,
+                        // 1-minute seeking
+                        KeyCode::Char('L') => app.skip_forward_long()?,
+                        KeyCode::Char('J') => app.skip_backward_long()?,
+                        _ => {}
+                    },
+                }
             }
         }
     }
@@ -156,7 +155,9 @@ fn ui(f: &mut Frame, app: &App) {
         .map(|feed| {
             let display_name = match &feed.state {
                 crate::feed::FeedState::Loading => format!("⟳ {}", feed.name),
-                crate::feed::FeedState::Loaded => format!("✓ {} ({})", feed.name, feed.episodes.len()),
+                crate::feed::FeedState::Loaded => {
+                    format!("✓ {} ({})", feed.name, feed.episodes.len())
+                }
                 crate::feed::FeedState::Error(_) => format!("✗ {}", feed.name),
             };
             ListItem::new(Line::from(display_name))
@@ -180,19 +181,33 @@ fn ui(f: &mut Frame, app: &App) {
     // Right panel - Episodes or info
     match app.current_screen {
         CurrentScreen::FeedList => {
-            let loading_count = app.feeds.iter().filter(|f| matches!(f.state, crate::feed::FeedState::Loading)).count();
-            let loaded_count = app.feeds.iter().filter(|f| matches!(f.state, crate::feed::FeedState::Loaded)).count();
-            let error_count = app.feeds.iter().filter(|f| matches!(f.state, crate::feed::FeedState::Error(_))).count();
-            
+            let loading_count = app
+                .feeds
+                .iter()
+                .filter(|f| matches!(f.state, crate::feed::FeedState::Loading))
+                .count();
+            let loaded_count = app
+                .feeds
+                .iter()
+                .filter(|f| matches!(f.state, crate::feed::FeedState::Loaded))
+                .count();
+            let error_count = app
+                .feeds
+                .iter()
+                .filter(|f| matches!(f.state, crate::feed::FeedState::Error(_)))
+                .count();
+
             let info_text = if app.feeds.is_empty() {
                 "No feeds configured!
 
 Edit ~/.config/ghetto-blaster.yml
 to add podcast feeds.
 
-Then press 'r' to refresh.".to_string()
+Then press 'r' to refresh."
+                    .to_string()
             } else if loading_count > 0 {
-                format!("Loading feeds... ({} loading, {} loaded, {} errors)
+                format!(
+                    "Loading feeds... ({} loading, {} loaded, {} errors)
 
 ⟳ = Loading
 ✓ = Loaded (episode count)
@@ -202,15 +217,18 @@ Navigation:
 • j/k or ↑/↓ - navigate
 • Enter - select feed
 • r - refresh feeds
-• q - quit", loading_count, loaded_count, error_count)
+• q - quit",
+                    loading_count, loaded_count, error_count
+                )
             } else {
                 "Navigation:
 • j/k or ↑/↓ - navigate
 • Enter - select feed
 • r - refresh feeds
-• q - quit".to_string()
+• q - quit"
+                    .to_string()
             };
-            
+
             let info = Paragraph::new(info_text)
                 .block(Block::default().title("Help").borders(Borders::ALL));
             f.render_widget(info, chunks[1]);
@@ -223,7 +241,7 @@ Navigation:
                     .map(|ep| {
                         let cache_indicator = if ep.is_cached { "💾 " } else { "🌐 " };
                         let title_with_cache = format!("{}{}", cache_indicator, ep.title);
-                        
+
                         ListItem::new(Line::from(vec![
                             Span::raw(title_with_cache),
                             Span::styled(
@@ -258,4 +276,3 @@ Navigation:
         }
     }
 }
-
src/player.rs
@@ -23,9 +23,9 @@ pub struct Player {
 
 impl Player {
     pub fn new() -> Result<Self> {
-        let (stream, stream_handle) = OutputStream::try_default()
-            .with_context(|| "Failed to create audio output stream")?;
-        
+        let (stream, stream_handle) =
+            OutputStream::try_default().with_context(|| "Failed to create audio output stream")?;
+
         Ok(Self {
             _stream: stream,
             stream_handle,
@@ -42,7 +42,6 @@ impl Player {
         })
     }
 
-
     pub fn _play(&mut self, url: &str) -> Result<()> {
         self.play_with_cache(url, None)
     }
@@ -55,7 +54,12 @@ impl Player {
         self.play_from_source(file_path, None, true)
     }
 
-    fn play_from_source(&mut self, source: &str, cache_info: Option<(&str, &str)>, is_local_file: bool) -> Result<()> {
+    fn play_from_source(
+        &mut self,
+        source: &str,
+        cache_info: Option<(&str, &str)>,
+        is_local_file: bool,
+    ) -> Result<()> {
         // Stop any currently playing audio
         self.stop();
 
@@ -89,7 +93,11 @@ impl Player {
         Ok(())
     }
 
-    fn start_playback_from_position(&mut self, audio_data: &[u8], position_seconds: f64) -> Result<()> {
+    fn start_playback_from_position(
+        &mut self,
+        audio_data: &[u8],
+        position_seconds: f64,
+    ) -> Result<()> {
         // Create a cursor from the audio data
         let cursor = Cursor::new(audio_data.to_vec());
 
@@ -102,7 +110,7 @@ impl Player {
             let sample_rate = decoded_source.sample_rate() as f64;
             let channels = decoded_source.channels() as f64;
             let samples_to_skip = (position_seconds * sample_rate * channels) as usize;
-            
+
             // Skip samples (this is approximate but works for seeking)
             for _ in 0..samples_to_skip {
                 if decoded_source.next().is_none() {
@@ -112,8 +120,8 @@ impl Player {
         }
 
         // Create a new sink
-        let sink = Sink::try_new(&self.stream_handle)
-            .with_context(|| "Failed to create audio sink")?;
+        let sink =
+            Sink::try_new(&self.stream_handle).with_context(|| "Failed to create audio sink")?;
 
         // Set volume
         sink.set_volume(self.volume);
@@ -135,7 +143,8 @@ impl Player {
         let response = reqwest::blocking::get(url)
             .with_context(|| format!("Failed to fetch audio from URL: {}", url))?;
 
-        let audio_data = response.bytes()
+        let audio_data = response
+            .bytes()
             .with_context(|| "Failed to read audio data")?;
 
         Ok(audio_data.to_vec())
@@ -144,22 +153,29 @@ impl Player {
     fn cache_path(&self, feed_name: &str, _episode_title: &str, url: &str) -> PathBuf {
         let home = dirs::home_dir().unwrap_or_else(|| PathBuf::from("/tmp"));
         let music_dir = home.join("Music").join(feed_name);
-        
+
         // Create filename from URL, handling query parameters
         let filename = url.split('/').last().unwrap_or("episode.mp3");
         let filename = filename.split('?').next().unwrap_or(filename);
-        
+
         // Sanitize filename
-        let safe_filename = filename.chars()
-            .map(|c| if c.is_alphanumeric() || c == '.' || c == '-' { c } else { '_' })
+        let safe_filename = filename
+            .chars()
+            .map(|c| {
+                if c.is_alphanumeric() || c == '.' || c == '-' {
+                    c
+                } else {
+                    '_'
+                }
+            })
             .collect::<String>();
-        
+
         music_dir.join(safe_filename)
     }
 
     fn load_from_cache(&self, feed_name: &str, episode_title: &str, url: &str) -> Result<Vec<u8>> {
         let cache_path = self.cache_path(feed_name, episode_title, url);
-        
+
         if cache_path.exists() && cache_path.metadata()?.len() > 0 {
             fs::read(&cache_path)
                 .with_context(|| format!("Failed to read cached file: {}", cache_path.display()))
@@ -168,20 +184,26 @@ impl Player {
         }
     }
 
-    fn download_and_cache(&self, url: &str, feed_name: &str, episode_title: &str) -> Result<Vec<u8>> {
+    fn download_and_cache(
+        &self,
+        url: &str,
+        feed_name: &str,
+        episode_title: &str,
+    ) -> Result<Vec<u8>> {
         let audio_data = self.download_audio(url)?;
         let cache_path = self.cache_path(feed_name, episode_title, url);
-        
+
         // Create directory if it doesn't exist
         if let Some(parent) = cache_path.parent() {
-            fs::create_dir_all(parent)
-                .with_context(|| format!("Failed to create cache directory: {}", parent.display()))?;
+            fs::create_dir_all(parent).with_context(|| {
+                format!("Failed to create cache directory: {}", parent.display())
+            })?;
         }
-        
+
         // Write to cache file
         fs::write(&cache_path, &audio_data)
             .with_context(|| format!("Failed to write cache file: {}", cache_path.display()))?;
-        
+
         Ok(audio_data)
     }
 
@@ -236,7 +258,7 @@ impl Player {
         // Calculate new position
         let current_pos = self.get_position();
         let new_position = (current_pos + seconds).max(0.0);
-        
+
         // Seek to absolute position
         self.seek_to(new_position)
     }
@@ -246,23 +268,23 @@ impl Player {
         if let Some(audio_data) = &self.current_source {
             let audio_data = audio_data.clone();
             let was_paused = self.is_paused;
-            
+
             // Stop current playback
             if let Some(sink) = &self.sink {
                 if let Ok(sink) = sink.lock() {
                     sink.stop();
                 }
             }
-            
+
             // Start playback from new position
             self.start_playback_from_position(&audio_data, position_seconds)?;
-            
+
             // If we were paused, pause again
             if was_paused {
                 self.toggle_pause()?;
             }
         }
-        
+
         Ok(())
     }
 
@@ -271,7 +293,7 @@ impl Player {
         self.seek(seconds)
     }
 
-    // Jump backward by a specific amount (e.g., 15 seconds)  
+    // Jump backward by a specific amount (e.g., 15 seconds)
     pub fn skip_backward(&mut self, seconds: f64) -> Result<()> {
         self.seek(-seconds)
     }
@@ -293,7 +315,7 @@ impl Player {
                 // If playing, subtract total paused duration
                 start_time.elapsed() - self.paused_duration
             };
-            
+
             // Add the seek position offset
             self.seek_position + elapsed.as_secs_f64()
         } else {
@@ -313,7 +335,7 @@ impl Player {
         format_duration(self.get_position())
     }
 
-    // Get duration as formatted time string  
+    // Get duration as formatted time string
     pub fn get_duration_formatted(&self) -> String {
         if let Some(duration) = self.get_duration() {
             format_duration(duration)
@@ -353,4 +375,3 @@ fn format_duration(seconds: f64) -> String {
         format!("{}:{:02}", minutes, secs)
     }
 }
-
src/podcast_ui.rs
@@ -13,16 +13,20 @@ pub fn render_now_playing_enhanced(f: &mut Frame, app: &App, area: Rect) {
         let main_chunks = Layout::default()
             .direction(Direction::Vertical)
             .constraints([
-                Constraint::Length(3),  // Title bar
-                Constraint::Length(4),  // Episode info header
-                Constraint::Min(8),     // Episode details/description
-                Constraint::Length(4),  // Progress and controls
+                Constraint::Length(3), // Title bar
+                Constraint::Length(4), // Episode info header
+                Constraint::Min(8),    // Episode details/description
+                Constraint::Length(4), // Progress and controls
             ])
             .split(area);
 
         // Title bar
         let title_bar = Paragraph::new("♪ GHETTO-BLASTER ♪")
-            .style(Style::default().fg(Color::Green).add_modifier(Modifier::BOLD))
+            .style(
+                Style::default()
+                    .fg(Color::Green)
+                    .add_modifier(Modifier::BOLD),
+            )
             .block(Block::default().borders(Borders::ALL))
             .alignment(ratatui::layout::Alignment::Center);
         f.render_widget(title_bar, main_chunks[0]);
@@ -78,7 +82,12 @@ fn render_episode_details(f: &mut Frame, app: &App, _track: &CurrentTrack, area:
 
 fn render_episode_metadata(f: &mut Frame, app: &App, area: Rect) {
     let mut metadata_lines = vec![
-        Line::from(Span::styled("Episode Metadata", Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD))),
+        Line::from(Span::styled(
+            "Episode Metadata",
+            Style::default()
+                .fg(Color::Yellow)
+                .add_modifier(Modifier::BOLD),
+        )),
         Line::from(""),
     ];
 
@@ -126,7 +135,7 @@ fn render_episode_metadata(f: &mut Frame, app: &App, area: Rect) {
             }
 
             metadata_lines.push(Line::from(""));
-            
+
             // Playback position
             let position_text = app.player.get_position_formatted();
             let duration_text = app.player.get_duration_formatted();
@@ -150,10 +159,10 @@ fn render_episode_description(f: &mut Frame, app: &App, area: Rect) {
     if let Some(feed) = app.feeds.get(app.selected_feed) {
         if let Some(episode) = feed.episodes.get(app.selected_episode) {
             description_text = episode.description.clone();
-            
+
             // Clean up HTML tags if present
             description_text = clean_html_tags(&description_text);
-            
+
             // Limit length for display
             if description_text.len() > 800 {
                 description_text.truncate(800);
@@ -164,7 +173,11 @@ fn render_episode_description(f: &mut Frame, app: &App, area: Rect) {
 
     let description_widget = Paragraph::new(description_text)
         .style(Style::default().fg(Color::White))
-        .block(Block::default().title("Episode Description").borders(Borders::ALL))
+        .block(
+            Block::default()
+                .title("Episode Description")
+                .borders(Borders::ALL),
+        )
         .wrap(Wrap { trim: true });
     f.render_widget(description_widget, area);
 }
@@ -172,7 +185,11 @@ fn render_episode_description(f: &mut Frame, app: &App, area: Rect) {
 fn render_progress_and_controls(f: &mut Frame, app: &App, area: Rect) {
     let progress_chunks = Layout::default()
         .direction(Direction::Horizontal)
-        .constraints([Constraint::Percentage(30), Constraint::Percentage(40), Constraint::Percentage(30)])
+        .constraints([
+            Constraint::Percentage(30),
+            Constraint::Percentage(40),
+            Constraint::Percentage(30),
+        ])
         .split(area);
 
     // Volume gauge
@@ -185,11 +202,12 @@ fn render_progress_and_controls(f: &mut Frame, app: &App, area: Rect) {
 
     // Progress bar with visual indicator
     let position = app.player.get_position();
-    let progress_text = format!("{} / {}", 
+    let progress_text = format!(
+        "{} / {}",
         app.player.get_position_formatted(),
         app.player.get_duration_formatted()
     );
-    
+
     // Create a simple text-based progress bar if we have duration
     let progress_display = if let Some(duration) = app.player.get_duration() {
         let progress_percentage = ((position / duration) * 100.0) as u16;
@@ -206,7 +224,7 @@ fn render_progress_and_controls(f: &mut Frame, app: &App, area: Rect) {
             .block(Block::default().title("Progress").borders(Borders::ALL))
             .alignment(ratatui::layout::Alignment::Center)
     };
-    
+
     f.render_widget(progress_display, progress_chunks[1]);
 
     // Controls help with seeking info
@@ -254,12 +272,11 @@ fn truncate_string(s: &str, max_len: usize) -> String {
     }
 }
 
-
 fn clean_html_tags(html: &str) -> String {
     // Simple HTML tag removal (basic implementation)
     let mut result = String::new();
     let mut in_tag = false;
-    
+
     for ch in html.chars() {
         match ch {
             '<' => in_tag = true,
@@ -268,10 +285,7 @@ fn clean_html_tags(html: &str) -> String {
             _ => {}
         }
     }
-    
+
     // Clean up extra whitespace
-    result
-        .split_whitespace()
-        .collect::<Vec<&str>>()
-        .join(" ")
-}
\ No newline at end of file
+    result.split_whitespace().collect::<Vec<&str>>().join(" ")
+}