Nessuna descrizione

Mindaugas Jarmolovičius b8c8157081 engine: pass stream URL to mpv; support modern mpv JSON-IPC 1 settimana fa
asot_player b8c8157081 engine: pass stream URL to mpv; support modern mpv JSON-IPC 1 settimana fa
tests ed5cffb4c6 initial 1 settimana fa
.gitignore ed5cffb4c6 initial 1 settimana fa
README.md ed5cffb4c6 initial 1 settimana fa
pyproject.toml ed5cffb4c6 initial 1 settimana fa
uv.lock ed5cffb4c6 initial 1 settimana fa

README.md

asot-player

A terminal player for the ASOT (A State of Trance — Armin van Buuren) weekly mix stream from miroppb.com/ASOT. It plays live MP3 streams with mpv, shows a small multi-line ANSI UI (episode number, current song, date, progress bar), and resumes the episode you left off on next launch.

ASOT 1298                      PLAYING
    Armin van Buuren - A State of Trance (2026)         07 Feb 2026
[██████████████████████░░░░░░] 00:12:34 / 05:28:00   80% ▓▓▓▓░░
space play/pause   ←/→ ±15s   ↑/↓ volume   m mute   n next  p prev  q quit

Features

  • mpv backend — playback, seeking, pause/mute/volume are all driven over mpv's JSON IPC socket.
  • Live song/track info without downloading — the ID3 header of each episode is probed with a Range: bytes=0-1048576 HTTP request. miroppb embeds per-track TIT2 frames (plus CHAP, TPE2, TALB, TYER, TXXX) inside the first MiB, so the "song if known" line usually updates within seconds.
  • Non-standard writer support — the parser tolerates the quirks of miroppb's tagger: frames separated by junk gaps, frame sizes declared one byte short, and UTF-16 payloads with repeated BOM pairs (see asot_player/id3.py).
  • Resumes on reopen — episode, URL, position, length, pause/mute/volume are persisted to ~/.config/asot-player/state.json (or $XDG_STATE_HOME/asot-player/, overridable via --state-dir / ASOT_STATE_DIR). Re-opening picks up the saved episode.
  • Auto-advance — when an episode ends (eof), the next episode in the feed starts automatically; at the end of the feed it reports and exits.
  • Minimal UI — a few fixed ANSI lines exactly terminal-width in both colour modes; no full-screen / ncurses.

Installation

Requires Python ≥ 3.10 and the system mpv binary (no Python dependencies):

# Arch            sudo pacman -S mpv
# Debian/Ubuntu   sudo apt-get install -y mpv
# Fedora          sudo dnf install mpv
# macOS           brew --cask install mpv

uv sync              # builds and installs this package (dev venv)
asot                 # ready to go

If mpv is installed somewhere outside PATH: asot --mpv-binary /path/to/mpv.

Usage

asot                          # latest episode (or resume saved position)
asot --ep 1298                # start a specific episode
asot --fresh                  # ignore saved state, start the latest episode
asot --width 78               # force terminal width (tests, narrow terminals)
asot --no-color               # disable ANSI colour
asot --fake                   # no mpv / no stream: exercises UI + state only
asot --fake-speed 1           # fake engine: seconds of "playback" per tick
asot --state-dir /tmp/x       # custom state directory (also: $ASOT_STATE_DIR)

Keys (single lines, no full-screen mode):

Key Action
space / t play / pause
m mute toggle
↑ / ↓ volume up / down (0.1 steps)
→ / ← forward / backward 15 s
n / p next / previous episode
q / Ctrl-C quit

How it works

  • Feed: miroppb.com/rss is parsed for <item> titles matching "Episode NNN" and enclosure URLs.
  • Metadata: fetch_meta(url) sends Range: bytes=0-1048576, parses the ID3 header from the returned bytes (asot_player/id3.py). Empty/failed probes just mean "no track info" — playback never blocks on metadata.
  • Engine: an mpv process per session with a private Unix socket; status is polled from playback-state, pause, mute, volume, playback-time, length.
  • State: small JSON file rewritten whenever position/length/mute/pause/volume change or every 3 s, so reopening (even minutes/hours later) resumes the right episode.

Development / testing

uv run python -m unittest tests.test_asot -v
# ~35 tests, sub-second (all network mocked; ID3 tests include a real 1 MiB
# episode head saved at /tmp/ep1298_head.bin when available)

# UI/state smoke test without mpv or a live stream:
timeout 60 uv run asot --fake --fake-speed 1 \
    --state-dir /tmp/stF --ep 1298 --width 78

Package layout is flat (no src/): asot_player/{main,engine,feed,id3,state,ui}.py, entry point asot = asot_player.main:main.

License

MIT.