Files
iradio/src/radio/app/player.rs
T

87 lines
2.5 KiB
Rust

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<dyn Fn(&str) -> io::Result<Child> + 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<Child> {
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;
}
}