added mpv command

This commit is contained in:
2025-08-05 21:53:53 +02:00
parent a2d14ca5ce
commit 8d216dbae6
2 changed files with 53 additions and 13 deletions
+1 -1
View File
@@ -8,7 +8,7 @@ async fn main() -> Result<()> {
terminal.clear()?; terminal.clear()?;
let mut app = App::default(); let mut app = App::default();
app.load_stations().await?; 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() { if let Err(err) = tui::restore() {
eprintln!( eprintln!(
+52 -12
View File
@@ -1,9 +1,11 @@
use crate::radio::station::StationInfo;
use color_eyre::{ use color_eyre::{
Result, Result,
eyre::{WrapErr, eyre}, eyre::{WrapErr, eyre},
}; };
use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind}; use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind};
use radiobrowser::RadioBrowserAPI; use radiobrowser::RadioBrowserAPI;
use ratatui::widgets::{List, ListItem, ListState};
use ratatui::{ use ratatui::{
DefaultTerminal, Frame, DefaultTerminal, Frame,
layout::{Constraint, Layout}, layout::{Constraint, Layout},
@@ -11,9 +13,7 @@ use ratatui::{
widgets::{Block, Paragraph}, widgets::{Block, Paragraph},
}; };
use std::io::{self}; use std::io::{self};
use tokio::process::Child;
use crate::radio::station::StationInfo;
use ratatui::widgets::{List, ListItem, ListState};
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum InputMode { pub enum InputMode {
@@ -29,6 +29,7 @@ pub struct App {
filtered_stations: Vec<StationInfo>, filtered_stations: Vec<StationInfo>,
search_query: String, search_query: String,
input_mode: InputMode, input_mode: InputMode,
player: Option<Child>,
} }
impl Default for App { impl Default for App {
@@ -44,15 +45,18 @@ impl Default for App {
filtered_stations: Vec::new(), filtered_stations: Vec::new(),
input_mode: InputMode::Normal, input_mode: InputMode::Normal,
search_query: String::new(), search_query: String::new(),
player: None,
} }
} }
} }
impl App { impl App {
pub fn run(&mut self, terminal: &mut DefaultTerminal) -> Result<()> { pub async fn run(&mut self, terminal: &mut DefaultTerminal) -> Result<()> {
while !self.exit { while !self.exit {
terminal.draw(|frame| self.draw(frame))?; terminal.draw(|frame| self.draw(frame))?;
self.handle_events().wrap_err("handle events failed")?; self.handle_events()
.await
.wrap_err("handle events failed")?;
} }
Ok(()) Ok(())
} }
@@ -108,30 +112,34 @@ impl App {
.block(Block::bordered().title("Info")); .block(Block::bordered().title("Info"));
frame.render_widget(paragraph, bottom_left); frame.render_widget(paragraph, bottom_left);
let search = Paragraph::new(format!("{}", self.search_query)) let mut search_string = self.search_query.clone();
.block(Block::bordered().title("Search")); 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(search, top_right);
frame.render_widget(Block::bordered().title("Played Songs"), bottom_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()? { match event::read()? {
Event::Key(key_event) if key_event.kind == KeyEventKind::Press => { Event::Key(key_event) if key_event.kind == KeyEventKind::Press => {
self.handle_key_event(key_event) self.handle_key_event(key_event).await
} }
_ => {} _ => {}
}; };
Ok(()) 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 { match self.input_mode {
InputMode::Normal => match key_event.code { InputMode::Normal => match key_event.code {
KeyCode::Char('/') => { KeyCode::Char('/') => {
self.input_mode = InputMode::Search; self.input_mode = InputMode::Search;
self.search_query.clear(); self.search_query.clear();
} }
KeyCode::Char('q') => self.exit(), KeyCode::Char('q') => self.exit().await,
KeyCode::Down | KeyCode::Char('j') => { KeyCode::Down | KeyCode::Char('j') => {
if let Some(i) = self.station_list_state.selected() { if let Some(i) = self.station_list_state.selected() {
let next = (i + 1).min(self.stations.len().saturating_sub(1)); let next = (i + 1).min(self.stations.len().saturating_sub(1));
@@ -144,6 +152,13 @@ impl App {
self.station_list_state.select(Some(prev)); 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 { 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; self.exit = true;
} }
@@ -218,4 +236,26 @@ impl App {
self.station_list_state.select(Some(0)); 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}");
}
}
}
} }