added song updates

This commit is contained in:
2025-08-16 08:36:31 +02:00
parent 8d216dbae6
commit 00faedb1fa
+77 -29
View File
@@ -12,8 +12,9 @@ use ratatui::{
style::Stylize,
widgets::{Block, Paragraph},
};
use std::io::{self};
use std::io;
use tokio::process::Child;
use tokio::sync::mpsc;
#[derive(Debug, PartialEq)]
pub enum InputMode {
@@ -30,6 +31,9 @@ pub struct App {
search_query: String,
input_mode: InputMode,
player: Option<Child>,
current_song: Option<String>,
played_songs: Vec<String>,
song_rx: Option<mpsc::UnboundedReceiver<String>>,
}
impl Default for App {
@@ -46,6 +50,9 @@ impl Default for App {
input_mode: InputMode::Normal,
search_query: String::new(),
player: None,
current_song: None,
played_songs: Vec::new(),
song_rx: None,
}
}
}
@@ -57,6 +64,7 @@ impl App {
self.handle_events()
.await
.wrap_err("handle events failed")?;
self.check_song_updates();
}
Ok(())
}
@@ -72,10 +80,10 @@ impl App {
Layout::vertical([Constraint::Percentage(10), Constraint::Percentage(90)]).areas(right);
let [top_left, bottom_left] =
Layout::vertical([Constraint::Percentage(50), Constraint::Percentage(50)]).areas(left);
Layout::vertical([Constraint::Percentage(10), Constraint::Percentage(90)]).areas(left);
// Render the left block (e.g., station selection)
let max_name_len = top_left.width.saturating_sub(4) as usize;
// Station list
let max_name_len = bottom_left.width.saturating_sub(4) as usize;
let station_items: Vec<ListItem> = self
.filtered_stations
.iter()
@@ -96,30 +104,34 @@ impl App {
frame.render_stateful_widget(
stations_list,
top_left,
bottom_left,
&mut self.station_list_state.clone(),
);
let station = self
.station_list_state
.selected()
.and_then(|i| self.filtered_stations.get(i));
let paragraph = Paragraph::new(
station
.map(|s| format!("Now selected: {}\n{}", s.name, s.url))
.unwrap_or_else(|| "No station selected".into()),
)
.block(Block::bordered().title("Info"));
frame.render_widget(paragraph, bottom_left);
// Info: show current song
let info_text = match &self.current_song {
Some(song) => format!("Now playing: {}", song),
None => "No song playing".to_string(),
};
let paragraph = Paragraph::new(info_text).block(Block::bordered().title("Info"));
frame.render_widget(paragraph, top_left);
// Search box
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);
// Played Songs list
let song_items: Vec<ListItem> = self
.played_songs
.iter()
.map(|s| ListItem::new(s.clone()))
.collect();
let songs_list = List::new(song_items).block(Block::bordered().title("Played Songs"));
frame.render_widget(songs_list, bottom_right);
}
async fn handle_events(&mut self) -> io::Result<()> {
@@ -132,6 +144,32 @@ impl App {
Ok(())
}
fn check_song_updates(&mut self) {
let mut new_songs = Vec::new();
if let Some(rx) = &mut self.song_rx {
while let Ok(song) = rx.try_recv() {
new_songs.push(song);
}
}
for song in new_songs {
self.update_current_song(song);
}
}
fn update_current_song(&mut self, song: String) {
if let Some(prev) = &self.current_song {
if prev != &song {
self.played_songs.insert(0, prev.clone());
if self.played_songs.len() > 20 {
self.played_songs.pop();
}
}
}
self.current_song = Some(song);
}
pub async fn handle_key_event(&mut self, key_event: KeyEvent) {
match self.input_mode {
InputMode::Normal => match key_event.code {
@@ -192,7 +230,6 @@ impl App {
let stations = api
.get_stations()
.country("Germany")
.limit("50")
.send()
.await
.map_err(|e| eyre!("Failed to fetch station: {e}"))?;
@@ -242,20 +279,31 @@ impl App {
let _ = child.kill().await;
}
let child = tokio::process::Command::new("mpv")
let (tx, rx) = mpsc::unbounded_channel();
self.song_rx = Some(rx);
let mut child = tokio::process::Command::new("mpv")
.arg(&url)
.arg("--no-video")
.stdout(std::process::Stdio::null())
.arg("--quiet")
.arg("--term-playing-msg=${media-title}")
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::null())
.spawn();
.spawn()
.expect("Failed to spawn mpv");
let mut stdout = child.stdout.take().expect("No stdout");
tokio::spawn(async move {
use tokio::io::{AsyncBufReadExt, BufReader};
let mut reader = BufReader::new(&mut stdout).lines();
while let Ok(Some(line)) = reader.next_line().await {
let title = line.trim().to_string();
if !title.is_empty() {
let _ = tx.send(title);
}
}
});
match child {
Ok(child) => {
self.player = Some(child);
}
Err(e) => {
eprintln!("Failed to play station: {e}");
}
}
}
}