diff --git a/src/main.rs b/src/main.rs index 557b6d4..16f6bf5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,7 +8,7 @@ async fn main() -> Result<()> { terminal.clear()?; let mut app = App::default(); app.load_stations().await?; - let app_result = app.run(&mut terminal); + let app_result = app.run(&mut terminal).await; if let Err(err) = tui::restore() { eprintln!( diff --git a/src/radio/app.rs b/src/radio/app.rs index 8b42715..06f7c0c 100644 --- a/src/radio/app.rs +++ b/src/radio/app.rs @@ -1,9 +1,11 @@ +use crate::radio::station::StationInfo; use color_eyre::{ Result, eyre::{WrapErr, eyre}, }; use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind}; use radiobrowser::RadioBrowserAPI; +use ratatui::widgets::{List, ListItem, ListState}; use ratatui::{ DefaultTerminal, Frame, layout::{Constraint, Layout}, @@ -11,9 +13,7 @@ use ratatui::{ widgets::{Block, Paragraph}, }; use std::io::{self}; - -use crate::radio::station::StationInfo; -use ratatui::widgets::{List, ListItem, ListState}; +use tokio::process::Child; #[derive(Debug, PartialEq)] pub enum InputMode { @@ -29,6 +29,7 @@ pub struct App { filtered_stations: Vec, search_query: String, input_mode: InputMode, + player: Option, } impl Default for App { @@ -44,15 +45,18 @@ impl Default for App { filtered_stations: Vec::new(), input_mode: InputMode::Normal, search_query: String::new(), + player: None, } } } impl App { - pub fn run(&mut self, terminal: &mut DefaultTerminal) -> Result<()> { + pub async fn run(&mut self, terminal: &mut DefaultTerminal) -> Result<()> { while !self.exit { terminal.draw(|frame| self.draw(frame))?; - self.handle_events().wrap_err("handle events failed")?; + self.handle_events() + .await + .wrap_err("handle events failed")?; } Ok(()) } @@ -108,30 +112,34 @@ impl App { .block(Block::bordered().title("Info")); frame.render_widget(paragraph, bottom_left); - let search = Paragraph::new(format!("{}", self.search_query)) - .block(Block::bordered().title("Search")); + let mut search_string = self.search_query.clone(); + if self.input_mode == InputMode::Search { + search_string.push('█'); + } + let search = Paragraph::new(search_string).block(Block::bordered().title("Search")); + frame.render_widget(search, top_right); frame.render_widget(Block::bordered().title("Played Songs"), bottom_right); } - fn handle_events(&mut self) -> io::Result<()> { + async fn handle_events(&mut self) -> io::Result<()> { match event::read()? { Event::Key(key_event) if key_event.kind == KeyEventKind::Press => { - self.handle_key_event(key_event) + self.handle_key_event(key_event).await } _ => {} }; Ok(()) } - fn handle_key_event(&mut self, key_event: KeyEvent) { + pub async fn handle_key_event(&mut self, key_event: KeyEvent) { match self.input_mode { InputMode::Normal => match key_event.code { KeyCode::Char('/') => { self.input_mode = InputMode::Search; self.search_query.clear(); } - KeyCode::Char('q') => self.exit(), + KeyCode::Char('q') => self.exit().await, KeyCode::Down | KeyCode::Char('j') => { if let Some(i) = self.station_list_state.selected() { let next = (i + 1).min(self.stations.len().saturating_sub(1)); @@ -144,6 +152,13 @@ impl App { self.station_list_state.select(Some(prev)); } } + KeyCode::Enter => { + if let Some(i) = self.station_list_state.selected() { + if let Some(station) = self.stations.get(i) { + self.play_station(station.url.clone()).await; + } + } + } _ => {} }, InputMode::Search => match key_event.code { @@ -163,7 +178,10 @@ impl App { } } - fn exit(&mut self) { + async fn exit(&mut self) { + if let Some(mut child) = self.player.take() { + let _ = child.kill().await; + } self.exit = true; } @@ -218,4 +236,26 @@ impl App { self.station_list_state.select(Some(0)); } } + + async fn play_station(&mut self, url: String) { + if let Some(mut child) = self.player.take() { + let _ = child.kill().await; + } + + let child = tokio::process::Command::new("mpv") + .arg(&url) + .arg("--no-video") + .stdout(std::process::Stdio::null()) + .stderr(std::process::Stdio::null()) + .spawn(); + + match child { + Ok(child) => { + self.player = Some(child); + } + Err(e) => { + eprintln!("Failed to play station: {e}"); + } + } + } }