From f2f79cc0d2d996a1a48a724431036b5958164a11 Mon Sep 17 00:00:00 2001 From: Christoffer Martinsson Date: Sat, 6 Dec 2025 16:07:50 +0100 Subject: [PATCH] Add video support and improve mpv process management - Enable video playback by removing --no-video flag - Add --profile=fast for better performance - Add --audio-display=no to prevent cover art windows - Implement mpv process respawn when closed - Add process death detection and cleanup - Show refresh status immediately in title bar - Fix playlist playback after clearing --- src/main.rs | 12 +++++-- src/player/mod.rs | 88 +++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 92 insertions(+), 8 deletions(-) diff --git a/src/main.rs b/src/main.rs index 646c1e3..50fcf6f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -81,6 +81,13 @@ async fn run_app( player: &mut player::Player, ) -> Result<()> { loop { + // Check if mpv process died (e.g., user closed video window) + if !player.is_process_alive() && state.player_state != PlayerState::Stopped { + state.player_state = PlayerState::Stopped; + state.current_position = 0.0; + state.current_duration = 0.0; + } + // Update player properties from MPV player.update_properties(); @@ -105,7 +112,7 @@ async fn run_app( if event::poll(std::time::Duration::from_millis(100))? { if let Event::Key(key) = event::read()? { if key.kind == KeyEventKind::Press { - handle_key_event(state, player, key).await?; + handle_key_event(terminal, state, player, key).await?; } } } @@ -118,7 +125,7 @@ async fn run_app( Ok(()) } -async fn handle_key_event(state: &mut AppState, player: &mut player::Player, key: KeyEvent) -> Result<()> { +async fn handle_key_event(terminal: &mut Terminal, state: &mut AppState, player: &mut player::Player, key: KeyEvent) -> Result<()> { // Handle search mode separately if state.search_mode { match key.code { @@ -261,6 +268,7 @@ async fn handle_key_event(state: &mut AppState, player: &mut player::Player, key } (KeyCode::Char('r'), _) => { state.is_refreshing = true; + terminal.draw(|f| ui::render(f, state))?; // Show "Refreshing library..." immediately tracing::info!("Rescanning..."); let cache_dir = cache::get_cache_dir()?; let new_cache = scanner::scan_paths(&state.config.scan_paths.paths)?; diff --git a/src/player/mod.rs b/src/player/mod.rs index 4866492..4cd5e19 100644 --- a/src/player/mod.rs +++ b/src/player/mod.rs @@ -27,8 +27,9 @@ impl Player { // Spawn MPV with IPC server let process = Command::new("mpv") .arg("--idle") - .arg("--no-video") .arg("--no-terminal") + .arg("--profile=fast") + .arg("--audio-display=no") // Don't show cover art for audio files .arg(format!("--input-ipc-server={}", socket_path.display())) .stdin(Stdio::null()) .stdout(Stdio::null()) @@ -54,14 +55,60 @@ impl Player { fn connect(&mut self) -> Result<()> { if self.socket.is_none() { - let stream = UnixStream::connect(&self.socket_path) - .context("Failed to connect to MPV IPC socket")?; - stream.set_nonblocking(true).ok(); - self.socket = Some(stream); + // Try to connect, if it fails, respawn mpv + match UnixStream::connect(&self.socket_path) { + Ok(stream) => { + stream.set_nonblocking(true).ok(); + self.socket = Some(stream); + } + Err(_) => { + // MPV probably died, respawn it + self.respawn()?; + let stream = UnixStream::connect(&self.socket_path) + .context("Failed to connect to MPV IPC socket after respawn")?; + stream.set_nonblocking(true).ok(); + self.socket = Some(stream); + } + } } Ok(()) } + fn respawn(&mut self) -> Result<()> { + // Kill old process if still running + self.process.kill().ok(); + self.process.wait().ok(); + + // Clean up old socket + std::fs::remove_file(&self.socket_path).ok(); + + // Spawn new MPV process + let process = Command::new("mpv") + .arg("--idle") + .arg("--no-terminal") + .arg("--profile=fast") + .arg("--audio-display=no") + .arg(format!("--input-ipc-server={}", self.socket_path.display())) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + .context("Failed to respawn MPV process")?; + + self.process = process; + self.socket = None; + self.is_idle = true; + self.position = 0.0; + self.duration = 0.0; + self.is_paused = false; + + // Wait for socket to be created and mpv to be ready + std::thread::sleep(Duration::from_millis(800)); + + tracing::info!("MPV process respawned"); + Ok(()) + } + fn send_command(&mut self, command: &str, args: &[Value]) -> Result<()> { self.connect()?; @@ -74,7 +121,17 @@ impl Player { if let Some(ref mut socket) = self.socket { let msg = format!("{}\n", cmd); - socket.write_all(msg.as_bytes()).context("Failed to write to socket")?; + // Ignore broken pipe errors (mpv closed) + if let Err(e) = socket.write_all(msg.as_bytes()) { + if e.kind() == std::io::ErrorKind::BrokenPipe { + self.socket = None; + self.is_idle = true; + // Clean up dead process + self.process.kill().ok(); + return Ok(()); + } + return Err(e).context("Failed to write to socket"); + } } Ok(()) @@ -178,6 +235,25 @@ impl Player { self.is_idle } + pub fn is_process_alive(&mut self) -> bool { + // Check if mpv process is still running + match self.process.try_wait() { + Ok(Some(_)) => { + // Process has exited - clean up socket + self.socket = None; + self.is_idle = true; + false + } + Ok(None) => true, // Process is still running + Err(_) => { + // Error checking, assume dead and clean up + self.socket = None; + self.is_idle = true; + false + } + } + } + pub fn seek(&mut self, seconds: f64) -> Result<()> { self.send_command("seek", &[json!(seconds), json!("relative")])?; Ok(())