Increase mpv audio buffer to 2 seconds to fix stuttering on WSLg. Ref: https://github.com/microsoft/wslg/issues/1257
6.5 KiB
CM Player - Music and Video TUI
Overview
A high-performance Rust-based TUI player for playing music and video files. Built to use mpv.
Key features
- Scan and cache file structure
- Made for use on low bandwidh vpn connections
- Use same TUI layout/theme as cm-dashboard
- Left panel is file structure
- Right panel is media status
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 endedpause(bool) - Playback is paused
Derive PlayerState:
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.
Configuration: ~/.config/cm-player/config.toml
[scan_paths]
paths = [
"/media/music",
"/media/videos"
]
Cache Structure: ~/.cache/cm-player/
file_tree.json- Full cached file structuremetadata.json- File metadata (duration, codec, size)
Workflow:
- User configures paths in config.toml
- Scanner scans configured paths → builds cache
- Left panel displays cached tree (instant, no filesystem access)
- Playback uses cached file paths
- Rescan command/keybinding to refresh cache
Features
Core Functionality
- Cache-Only Operation - Browse media from cached file structure, no direct filesystem access
- MPV Integration - Playback via MPV IPC (subprocess), no library linking required
- Playlist Management - Play single files, entire directories, or marked files
- File Marking - Mark multiple files to create custom playlists
- Tree Navigation - Vim-style keybindings for browsing collapsible directory structure
- Manual Library Refresh - User-controlled scanning with 'r' key
UI Layout (cm-dashboard style)
- Title Bar - Shows player state (Playing/Paused/Stopped/Refreshing)
- Left Panel - Cached media file tree with vim navigation
- Right Panel - Player info (progress, volume) + Playlist view
- Status Bar - Centered help text with all keybindings
Keybindings
j/kor↑↓- Navigate filesh/l- Collapse/expand directoriest- Mark/unmark filec- Clear all marksEnter- Play (priority: marked files > directory > single file)Space- Pause/resume← →- Seek backward/forward 10 seconds+ -- Volume up/down by 5%n/p- Next/previous track in playlistr- Rescan library (manual refresh)q- Quit
API for OS-Wide Shortcuts
cm-player provides a Unix socket API for external control, allowing integration with OS-wide media keys and custom shortcuts.
Architecture
- Single Binary:
cm-playeracts as both TUI server and CLI client - IPC: Unix socket at
$XDG_RUNTIME_DIR/cm-player.sock(or/tmp/cm-player.sock) - Protocol: JSON commands over Unix socket
Usage
# Start TUI (server mode)
cm-player
# Send commands to running instance (client mode)
cm-player play-pause
cm-player next
cm-player prev
cm-player stop
cm-player volume-up
cm-player volume-down
cm-player volume 50
cm-player seek-forward 30
cm-player seek-backward 10
cm-player quit
OS-Wide Keyboard Shortcuts
i3/sway config:
bindsym XF86AudioPlay exec cm-player play-pause
bindsym XF86AudioNext exec cm-player next
bindsym XF86AudioPrev exec cm-player prev
bindsym XF86AudioStop exec cm-player stop
KDE/GNOME:
Add custom shortcuts pointing to cm-player <command>
JSON Protocol
Commands are JSON objects with a command field:
{"command": "play-pause"}
{"command": "next"}
{"command": "prev"}
{"command": "stop"}
{"command": "volume-up"}
{"command": "volume-down"}
{"command": "volume-set", "volume": 50}
{"command": "seek-forward", "seconds": 30}
{"command": "seek-backward", "seconds": 10}
{"command": "get-status"}
{"command": "quit"}
Responses:
{"success": true, "message": null, "data": null}
{"success": false, "message": "error details", "data": null}
Technical Details
- MPV IPC - Communicates with mpv via Unix socket and JSON protocol
- No Version Lock - Uses mpv binary, not libmpv library (avoids version mismatch)
- Logging - Redirected to
/tmp/cm-player.logto avoid TUI interference - Auto-advance - Automatically plays next track when current ends
Automated Binary Release System
CM Player uses automated binary releases instead of source builds.
Creating New Releases
cd ~/projects/cm-player
git tag v0.1.X
git push origin v0.1.X
This automatically:
- Builds static binaries with
RUSTFLAGS="-C target-feature=+crt-static" - Creates GitHub-style release with tarball
- Uploads binaries via Gitea API
NixOS Configuration Updates
Edit ~/projects/nixosbox/hosts/services/cm-player.nix:
version = "v0.1.X";
src = pkgs.fetchurl {
url = "https://gitea.cmtec.se/cm/cm-player/releases/download/${version}/cm-player-linux-x86_64.tar.gz";
sha256 = "sha256-NEW_HASH_HERE";
};
Important Communication Guidelines
Keep responses concise and focused. Avoid extensive implementation summaries unless requested.
Commit Message Guidelines
NEVER mention:
- Claude or any AI assistant names
- Automation or AI-generated content
- Any reference to automated code generation
ALWAYS:
- Focus purely on technical changes and their purpose
- Use standard software development commit message format
- Describe what was changed and why, not how it was created
- Write from the perspective of a human developer
Examples:
- ❌ "Generated with Claude Code"
- ❌ "AI-assisted implementation"
- ❌ "Automated refactoring"
- ✅ "Implement maintenance mode for backup operations"
- ✅ "Restructure storage widget with improved layout"
- ✅ "Update CPU thresholds to production values"
Implementation Rules
NEVER:
- Copy/paste ANY code from legacy implementations
- Create files unless absolutely necessary for achieving goals
- Create documentation files unless explicitly requested
ALWAYS:
- Prefer editing existing files to creating new ones
- Follow existing code conventions and patterns
- Use existing libraries and utilities
- Follow security best practices