Christoffer Martinsson 59f9f548c1
All checks were successful
Build and Release / build-and-release (push) Successful in 1m1s
Add playlist bounds validation and search mode visual indicator
- Add bounds checking to prevent accessing invalid playlist indices
- Yellow/orange selection bar when in search mode
- Validate playlist index after navigation operations
- Handle empty playlists gracefully
2025-12-07 16:02:44 +01:00
2025-12-06 16:56:15 +01:00

cm-player

A high-performance terminal UI music and video player written in Rust, designed for low-bandwidth VPN environments with cache-only operation.

Features

  • Cache-only architecture: Scans media directories once and operates from cache, ideal for low-bandwidth VPN connections
  • Vim-style navigation: Familiar keybindings (j/k/h/l) for efficient navigation
  • Incremental fuzzy search: Real-time search with Tab completion, similar to vim's command-line search
  • Directory tree browsing: Collapsible folder structure with lazy scrolling
  • Playlist management: Mark multiple files (vim-style 'v' key) to create custom playlists
  • MPV integration: Video and audio playback via MPV subprocess with IPC control
  • Dual-panel layout: File browser on left, playlist on right
  • Auto-advance: Automatically plays next track when current track ends

Architecture

Core Components

State Management (src/state/mod.rs)

  • Maintains flattened file tree from cache for efficient rendering
  • Handles directory expansion/collapse state
  • Manages playlist and playback position
  • Implements fuzzy search with folder priority boost

MPV IPC Integration (src/player/mod.rs)

  • Spawns MPV as subprocess with Unix socket IPC
  • Automatic process respawn when MPV exits (user closes window)
  • Non-blocking socket communication with JSON protocol
  • Tracks playback state, position, duration via property observation

Cache System (src/cache/mod.rs)

  • XDG-compliant cache storage: ~/.cache/cm-player/
  • Serializes directory tree structure with serde_json
  • Supports manual refresh with 'r' key
  • Scans multiple configured paths

UI Rendering (src/ui/mod.rs)

  • Three-section layout: title bar (1 line) | content | status bar (1 line)
  • Ratatui framework with crossterm backend
  • Theme matching cm-dashboard color scheme
  • Real-time playback progress in title bar

Technical Details

Fuzzy Search Algorithm

// Scoring system:
// - Characters must appear in order (not necessarily consecutive)
// - Consecutive matches: +10 bonus per character
// - Word boundary matches: +15 bonus
// - Gap penalty: -1 per character between matches
// - Folder priority: +50 score boost
// - Length bonus: 100 - text_length

MPV Communication

  • IPC socket: /tmp/cm-player-{pid}.sock
  • Commands: { "command": ["loadfile", "path"] }
  • Property observation: time-pos, duration, pause, idle-active
  • Process lifecycle: spawn -> connect -> command -> observe -> respawn on exit

Lazy Scrolling

  • Selection stays in place until reaching viewport edge
  • Scroll offset only updates when necessary
  • Page navigation with Ctrl-D/U (half-page jumps)

Installation

Pre-built Binary (NixOS)

# hosts/common/cm-player.nix is automatically included
# mpv is installed as dependency

From Release

curl -L https://gitea.cmtec.se/cm/cm-player/releases/latest/download/cm-player-linux-x86_64 -o cm-player
chmod +x cm-player
sudo mv cm-player /usr/local/bin/

Build from Source

git clone https://gitea.cmtec.se/cm/cm-player.git
cd cm-player
cargo build --release
./target/release/cm-player

Static linking (for portability):

export RUSTFLAGS="-C target-feature=+crt-static"
cargo build --release --target x86_64-unknown-linux-gnu

Configuration

Config File

Location: ~/.config/cm-player/config.toml

[scan_paths]
paths = [
    "/mnt/music",
    "/mnt/movie",
    "/mnt/tv"
]

First Run

  1. Create config file with media paths
  2. Launch cm-player
  3. Press 'r' to scan directories and build cache
  4. Cache stored at ~/.cache/cm-player/cache.json

Keybindings

Navigation

  • j / k / / - Move selection down/up
  • h - Collapse directory
  • l - Expand directory
  • Ctrl-D - Page down (half page)
  • Ctrl-U - Page up (half page)
  • / - Enter search mode (fuzzy find)
  • Tab - Next search result
  • Shift-Tab - Previous search result
  • Enter - Execute search and enable n/N cycling
  • n - Next search match (after Enter) or next track
  • N - Previous search match
  • Esc - Clear search results

Playlist

  • v - Mark/unmark file or directory
  • a - Add marked files to playlist
  • c - Clear playlist

Playback

  • Enter - Play selected file/folder (uses marks if any)
  • Space - Pause/Resume
  • s - Stop playback
  • p - Previous track
  • n - Next track (when not in search results)
  • / - Seek backward/forward 10 seconds
  • + / = - Increase volume
  • - - Decrease volume

System

  • r - Rescan media directories and rebuild cache
  • q - Quit

Development

Project Structure

cm-player/
├── src/
│   ├── main.rs           # Event loop and key handling
│   ├── state/mod.rs      # App state and search logic
│   ├── player/mod.rs     # MPV IPC integration
│   ├── ui/
│   │   ├── mod.rs        # TUI rendering
│   │   └── theme.rs      # Color scheme
│   ├── cache/mod.rs      # Cache serialization
│   ├── scanner/mod.rs    # Directory scanning
│   └── config/mod.rs     # Config file handling
├── .gitea/workflows/
│   └── release.yml       # CI/CD pipeline
└── Cargo.toml

Dependencies

  • ratatui - Terminal UI framework
  • crossterm - Terminal backend
  • tokio - Async runtime for event loop
  • serde/serde_json - Cache serialization
  • walkdir - Directory traversal
  • anyhow/thiserror - Error handling

Logging

Logs written to /tmp/cm-player.log to avoid interfering with TUI:

tracing::info!("Playing: {:?}", path);
tail -f /tmp/cm-player.log

Release Process

Automated CI/CD

Releases are fully automated via Gitea Actions:

# Update version in Cargo.toml
vim Cargo.toml

# Commit and tag
git add Cargo.toml
git commit -m "Bump version to v0.1.X"
git push
git tag v0.1.X
git push origin v0.1.X

Workflow steps:

  1. Build static binary with RUSTFLAGS="-C target-feature=+crt-static"
  2. Create GitHub-style release on Gitea
  3. Upload binary and tarball as release assets
  4. Calculate SHA256 hash from local tarball
  5. Clone nixosbox repository
  6. Update hosts/common/cm-player.nix with new version and hash
  7. Commit and push to nixosbox

Manual NixOS Update

If workflow fails, update manually:

cd ~/projects/nixosbox

# Download and hash the tarball
curl -L https://gitea.cmtec.se/cm/cm-player/releases/download/v0.1.X/cm-player-linux-x86_64.tar.gz -o /tmp/cm-player.tar.gz
sha256sum /tmp/cm-player.tar.gz | cut -d' ' -f1
# Convert hex to base64: python3 -c "import base64, binascii; print(base64.b64encode(binascii.unhexlify('HEX_HASH')).decode())"

# Edit hosts/common/cm-player.nix
vim hosts/common/cm-player.nix
# Update version and sha256

git add hosts/common/cm-player.nix
git commit -m "Update cm-player to v0.1.X"
git push

MPV Configuration

Default MPV flags:

  • --idle - Keep MPV running between tracks
  • --no-terminal - Disable MPV's terminal output
  • --profile=fast - Use fast decoding profile
  • --audio-display=no - Disable cover art for audio files
  • --input-ipc-server=/tmp/cm-player-{pid}.sock - Enable IPC

Troubleshooting

MPV window closes immediately

Check MPV installation: mpv --version

Scan shows no files

  • Verify paths in ~/.config/cm-player/config.toml
  • Press 'r' to rescan
  • Check logs: tail -f /tmp/cm-player.log

Hash mismatch on NixOS rebuild

Video playback issues

MPV process respawns automatically if closed. If issues persist:

  1. Stop playback with 's'
  2. Restart cm-player
  3. Check /tmp/cm-player.log for errors

License

Copyright (c) 2025 CM Tech

Description
No description provided
Readme 208 KiB
2025-12-07 16:02:44 +01:00
Languages
Rust 99.7%
Nix 0.3%