- Rust 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| src | ||
| tests/fixtures | ||
| .gitignore | ||
| AGENTS.md | ||
| Cargo.lock | ||
| Cargo.toml | ||
| README.md | ||
lp
An offline terminal music player in development, using Rust and Ratatui.
Run
Run cargo run -- /path/to/music in an interactive terminal with an available
audio output device. Linux builds require ALSA and D-Bus development libraries.
Recursively discover FLAC and MP3 files without changing the library. File extensions are matched case-insensitively; symlinks are skipped, including an explicitly supplied symlink root. Scan errors appear with a warning count and the most recent diagnostic. Results are sorted by path, preserving the selected file as new results arrive.
j/k, arrows: select;Home/End: first / lastEnter: play selection, capturing the currently filtered library orderSpace: pause / resume; start the current target or first queued entry when stoppeds: stopn/p: next / previous, without wrappingh/l: seek backward / forward 5 seconds+/-: volumea/Ain the library: append selection / all filtered tracks to the session queueTab: switch library / queueEnterin the queue: play the selected queue entryJ/Kin the queue: move the selected entry down / upd/Deletein the queue: remove selection;C: clear queue/: edit a case-insensitive title, artist, album, or relative-path filterEnterwhile filtering: accept;Esc: cancel editing or clear an accepted filterCtrl-L: clear the filter in either view or while editing?: helpq: quit outside filtering; close help when help is openCtrl-C: quit in any mode
> marks selection; * marks the current playback target. Filtering and
navigation do not change active playback or its captured order. Playback
advances automatically and stops at the end of that order. A failed track
stops playback with a diagnostic rather than being silently skipped.
Next/previous preserve paused or stopped state and do nothing at the order's
boundaries. Natural completion stops at the end.
Seeking preserves the current track and paused/playing state, clamps at zero and known duration, and does nothing while stopped. At the end, the track stays selected until you seek back or explicitly resume to allow completion.
Session queue
Build a queue with a or A, then press Tab and Enter to play from it.
Adding tracks does not start playback. Duplicate paths are distinct queue
entries. Reordering the playing entry preserves its identity and playback
position; subsequent advancement uses the edited order. Removing the current
queue entry, or clearing its queue, stops playback without choosing another.
Library playback and queue construction are separate: pressing Enter in the
library captures the filtered library order and leaves the constructed queue
available. Editing or clearing an inactive queue does not interrupt library
playback. Queue contents are discarded when you exit; no library files are
written.
playerctl / MPRIS2
With a Linux session D-Bus available, use playerctl --player=lp to control the
same transport as the terminal. First choose a track or build a queue in lp.
playerctl --player=lp play-pause
playerctl --player=lp pause
playerctl --player=lp play
playerctl --player=lp next
playerctl --player=lp previous
playerctl --player=lp volume 0.5
playerctl --player=lp stop
playerctl --player=lp status
playerctl --player=lp metadata
playerctl --player=lp position
The bus name is org.mpris.MediaPlayer2.lp; additional instances use
org.mpris.MediaPlayer2.lp.instance<PID>. Status, source position, known duration,
metadata, volume, and transport capabilities reflect the playback worker.
Unknown metadata fields are omitted. Remote seeking and opening URIs are not
implemented; repeat and shuffle report off. Terminal seeks emit Seeked updates.
If the session bus is unavailable, lp displays a warning and you can use terminal
playback normally.
Metadata and playback
The table displays available title, artist, album, and duration metadata, with
filename fallbacks and explicit unknown values. Elapsed time comes from the
audio backend's source position, not a wall-clock estimate. Unknown total
durations appear as --:--. Scanning and audio each use a separate worker;
audio output and decoding do not run in the terminal event loop.
Header metadata reads are capped at 1 MiB. Oversized or invalid metadata produces a warning and a filename fallback; the candidate can still be played. Trailing-only metadata such as ID3v1 is not scanned. MP3 durations without a reliable frame-count header are unknown rather than bitrate estimates. Symphonia performs decoding, with Rodio handling output. Decoder and read errors, FLAC checksum failures, and mismatches against declared audio frame counts stop playback with a path-bearing diagnostic. Tracks without a frame count or checksum cannot be checked for every missing frame; some MP3 damage may also be hidden by the demuxer's resynchronization. After a nonzero seek, skipped PCM prevents full-stream checksum and frame-count verification; packet and decode errors are still reported. Seeking to zero restores full-stream verification.
Development
Run cargo fmt --check, cargo test, and cargo clippy --all-targets.
Ordinary tests do not require audio hardware. To exercise real output, provide
three-second local fixtures named a.flac and b.mp3 and run:
LP_AUDIO_TEST_DIR=/path/to/fixtures cargo test playback::tests::real_output -- --ignored --nocapture
This test produces sound and exercises transport transitions and completion. Verify actual output independently, for example by recording the output sink's monitor, rather than treating successful decoder construction as proof of sound.
The isolated D-Bus integration test uses the real service and transport without opening an audio device:
dbus-run-session -- cargo test mpris::tests::session_bus -- --ignored
Codec fixtures and regeneration commands are in tests/fixtures/README.md.