Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 55e3f04e2c | |||
| 1c2c942e4b | |||
| 3e7707e883 | |||
| b59d1aed65 |
23
CLAUDE.md
23
CLAUDE.md
@@ -14,6 +14,29 @@ A high-performance Rust-based TUI player for playing music and video files. Buil
|
||||
|
||||
## 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
|
||||
|
||||
**CRITICAL:** Left panel shows ONLY cached data. Never browse filesystem directly during operation.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "cm-player"
|
||||
version = "0.1.19"
|
||||
version = "0.1.22"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
||||
19
src/main.rs
19
src/main.rs
@@ -96,6 +96,8 @@ fn action_play_selection(state: &mut AppState, player: &mut player::Player) -> R
|
||||
state.play_selection();
|
||||
if let Some(ref path) = state.current_file {
|
||||
player.play(path)?;
|
||||
// Explicitly resume playback in case MPV was paused
|
||||
player.resume()?;
|
||||
state.player_state = PlayerState::Playing;
|
||||
player.update_metadata();
|
||||
tracing::info!("Playing: {:?} (playlist: {} tracks)", path, state.playlist.len());
|
||||
@@ -126,6 +128,7 @@ fn action_toggle_play_pause(state: &mut AppState, player: &mut player::Player) -
|
||||
state.player_state = PlayerState::Playing;
|
||||
if let Some(ref path) = state.current_file {
|
||||
player.play(path)?;
|
||||
player.resume()?;
|
||||
player.update_metadata();
|
||||
tracing::info!("Restarting playback: {:?}", path);
|
||||
}
|
||||
@@ -156,6 +159,8 @@ fn action_remove_from_playlist(state: &mut AppState, player: &mut player::Player
|
||||
state.current_file = Some(state.playlist[state.playlist_index].clone());
|
||||
if let Some(ref path) = state.current_file {
|
||||
player.play(path)?;
|
||||
// Explicitly resume playback in case MPV was paused
|
||||
player.resume()?;
|
||||
player.update_metadata();
|
||||
}
|
||||
}
|
||||
@@ -171,6 +176,7 @@ fn action_play_from_playlist(state: &mut AppState, player: &mut player::Player,
|
||||
PlayerState::Playing => {
|
||||
if let Some(ref path) = state.current_file {
|
||||
player.play(path)?;
|
||||
player.resume()?;
|
||||
player.update_metadata();
|
||||
tracing::info!("Jumped to track: {:?}", path);
|
||||
}
|
||||
@@ -187,6 +193,7 @@ fn action_play_from_playlist(state: &mut AppState, player: &mut player::Player,
|
||||
state.player_state = PlayerState::Playing;
|
||||
if let Some(ref path) = state.current_file {
|
||||
player.play(path)?;
|
||||
player.resume()?;
|
||||
player.update_metadata();
|
||||
tracing::info!("Started playing track: {:?}", path);
|
||||
}
|
||||
@@ -196,6 +203,8 @@ fn action_play_from_playlist(state: &mut AppState, player: &mut player::Player,
|
||||
state.player_state = PlayerState::Playing;
|
||||
if let Some(ref path) = state.current_file {
|
||||
player.play(path)?;
|
||||
// Explicitly resume playback in case MPV was paused
|
||||
player.resume()?;
|
||||
player.update_metadata();
|
||||
tracing::info!("Playing from playlist: {:?}", path);
|
||||
}
|
||||
@@ -295,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)
|
||||
// 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();
|
||||
// play_next() handles the play mode and may stop if in Normal mode at end
|
||||
if state.player_state == PlayerState::Playing {
|
||||
@@ -306,6 +316,7 @@ async fn run_app<B: ratatui::backend::Backend>(
|
||||
last_position = 0.0;
|
||||
|
||||
player.play(path)?;
|
||||
player.resume()?;
|
||||
}
|
||||
// Update metadata immediately when track changes
|
||||
player.update_metadata();
|
||||
@@ -519,6 +530,7 @@ async fn handle_key_event<B: ratatui::backend::Backend>(terminal: &mut Terminal<
|
||||
// Keep playing
|
||||
if let Some(ref path) = state.current_file {
|
||||
player.play(path)?;
|
||||
player.resume()?;
|
||||
player.update_metadata(); // Update metadata immediately
|
||||
tracing::info!("Next track: {:?}", path);
|
||||
}
|
||||
@@ -557,6 +569,7 @@ async fn handle_key_event<B: ratatui::backend::Backend>(terminal: &mut Terminal<
|
||||
// Keep playing
|
||||
if let Some(ref path) = state.current_file {
|
||||
player.play(path)?;
|
||||
player.resume()?;
|
||||
player.update_metadata(); // Update metadata immediately
|
||||
tracing::info!("Previous track: {:?}", path);
|
||||
}
|
||||
@@ -614,12 +627,12 @@ async fn handle_key_event<B: ratatui::backend::Backend>(terminal: &mut Terminal<
|
||||
state.move_selection_down();
|
||||
}
|
||||
}
|
||||
(KeyCode::Char('h'), _) => {
|
||||
(KeyCode::Char('h'), _) | (KeyCode::Left, _) => {
|
||||
if !state.focus_playlist {
|
||||
state.collapse_selected();
|
||||
}
|
||||
}
|
||||
(KeyCode::Char('l'), _) => {
|
||||
(KeyCode::Char('l'), _) | (KeyCode::Right, _) => {
|
||||
if !state.focus_playlist {
|
||||
state.expand_selected();
|
||||
}
|
||||
|
||||
@@ -327,6 +327,10 @@ impl Player {
|
||||
self.is_idle
|
||||
}
|
||||
|
||||
pub fn is_paused(&self) -> bool {
|
||||
self.is_paused
|
||||
}
|
||||
|
||||
pub fn is_process_alive(&mut self) -> bool {
|
||||
// Check if mpv process is still running
|
||||
match self.process.try_wait() {
|
||||
|
||||
Reference in New Issue
Block a user