2 Commits

Author SHA1 Message Date
55e3f04e2c Fix auto-play next track during pause/unpause transitions
All checks were successful
Build and Release / build-and-release (push) Successful in 53s
When rapidly pressing play/pause, MPV briefly reports idle-active
as true during state transitions. Combined with our player_state
being set to Playing after unpause, this incorrectly triggered the
auto-play next track logic.

Fix: Add is_paused() check to auto-play condition to ensure we only
advance to next track when the current track actually ends, not
during pause state transitions.
2025-12-11 16:20:14 +01:00
1c2c942e4b Document state management architecture principles
Add critical guidelines for deriving player state from MPV rather
than maintaining duplicate state. Documents the single source of
truth pattern to prevent state synchronization bugs.
2025-12-11 16:11:45 +01:00
4 changed files with 30 additions and 2 deletions

View File

@@ -14,6 +14,29 @@ A high-performance Rust-based TUI player for playing music and video files. Buil
## Architecture ## Architecture
### State Management
**CRITICAL:** Player state must be derived from MPV, not maintained separately.
**Single Source of Truth:** MPV properties via IPC
- `idle-active` (bool) - No file loaded or file ended
- `pause` (bool) - Playback is paused
**Derive PlayerState:**
```rust
if player.is_idle PlayerState::Stopped
if !player.is_idle && player.is_paused PlayerState::Paused
if !player.is_idle && !player.is_paused PlayerState::Playing
```
**Rationale:**
- Eliminates state synchronization bugs
- MPV is always the authoritative source
- No need to update state in multiple places
- Simpler auto-play logic
**Anti-pattern:** DO NOT maintain `state.player_state` that can desync from MPV
### Cache-Only Operation ### Cache-Only Operation
**CRITICAL:** Left panel shows ONLY cached data. Never browse filesystem directly during operation. **CRITICAL:** Left panel shows ONLY cached data. Never browse filesystem directly during operation.

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "cm-player" name = "cm-player"
version = "0.1.21" version = "0.1.22"
edition = "2021" edition = "2021"
[dependencies] [dependencies]

View File

@@ -304,7 +304,8 @@ async fn run_app<B: ratatui::backend::Backend>(
// Check if track ended and play next (but only if track was actually loaded AND played) // Check if track ended and play next (but only if track was actually loaded AND played)
// Require position > 0.5 to ensure track actually started playing (not just loaded) // Require position > 0.5 to ensure track actually started playing (not just loaded)
if player.is_idle() && state.player_state == PlayerState::Playing && state.current_duration > 0.0 && state.current_position > 0.5 { // Also check !is_paused to avoid triggering during pause/unpause transitions
if player.is_idle() && !player.is_paused() && state.player_state == PlayerState::Playing && state.current_duration > 0.0 && state.current_position > 0.5 {
state.play_next(); state.play_next();
// play_next() handles the play mode and may stop if in Normal mode at end // play_next() handles the play mode and may stop if in Normal mode at end
if state.player_state == PlayerState::Playing { if state.player_state == PlayerState::Playing {

View File

@@ -327,6 +327,10 @@ impl Player {
self.is_idle self.is_idle
} }
pub fn is_paused(&self) -> bool {
self.is_paused
}
pub fn is_process_alive(&mut self) -> bool { pub fn is_process_alive(&mut self) -> bool {
// Check if mpv process is still running // Check if mpv process is still running
match self.process.try_wait() { match self.process.try_wait() {