Christoffer Martinsson ffe7cd0090
All checks were successful
Build and Release / build-and-release (push) Successful in 54s
Fix time display to update smoothly
Change position update logic to only trigger redraw when the
displayed value (rounded to seconds) changes, not when the raw
float value changes. This eliminates jumpy time display and
reduces unnecessary redraws.
2025-12-09 12:33:52 +01:00
2025-12-09 12:33:52 +01:00
2025-12-09 12:33:52 +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 300 KiB
2025-12-09 12:33:52 +01:00
Languages
Rust 99.8%
Nix 0.2%