use super::App; use crate::radio::station::StationInfo; use std::io; use tokio::process::Child; use tokio::sync::mpsc; #[allow(clippy::type_complexity)] pub(super) struct SpawnFn(Box io::Result + Send + Sync>); impl std::fmt::Debug for SpawnFn { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str("SpawnFn") } } pub(super) fn new_mpv_spawn() -> SpawnFn { SpawnFn(Box::new(mpv_spawn)) } fn mpv_spawn(url: &str) -> io::Result { tokio::process::Command::new("mpv") .arg(url) .arg("--no-video") .arg("--quiet") .arg("--term-playing-msg=${media-title}") .stdout(std::process::Stdio::piped()) .stderr(std::process::Stdio::null()) .spawn() } impl App { pub(super) async fn play_station(&mut self, station: StationInfo) { if let Some(mut child) = self.player.take() { let _ = child.kill().await; } self.error_message = None; self.current_song = None; self.current_station = Some(station.clone()); let Some(SpawnFn(ref spawn)) = self.spawn_player else { return; }; let (tx, rx) = mpsc::unbounded_channel(); self.song_rx = Some(rx); match spawn(&station.url) { Err(e) => { self.error_message = Some(format!("Failed to start mpv: {e}")); self.current_station = None; } Ok(mut child) => { let mut stdout = child.stdout.take().expect("mpv 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); } } }); self.player = Some(child); } } } pub(super) async fn stop_playback(&mut self) { if let Some(mut child) = self.player.take() { let _ = child.kill().await; } self.current_station = None; self.current_song = None; self.song_rx = None; self.error_message = None; } pub(super) async fn exit(&mut self) { if let Some(mut child) = self.player.take() { let _ = child.kill().await; } self.exit = true; } }