From 5eb60d378cabb5014b8e1de42ead2c73a5999880 Mon Sep 17 00:00:00 2001 From: beefcarver Date: Tue, 3 Nov 2026 20:31:18 -0500 Subject: [PATCH] feat(catalogue): tune a single-station catalogue on switch --- README.md | 4 ++- beefy_player/app.py | 8 ++++- tests/test_providers.py | 69 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index eb23fe4..eaee379 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,9 @@ implemented here from public endpoints and open protocols. - **Favourites** that float to the top, remembered between runs. - **Pluggable catalogues** — one `providers` module per radio network, with a `p` picker in the UI, `--provider` on the command line, and the choice - remembered per catalogue (including the last station you tuned). + remembered per catalogue (including the last station you tuned). Switching to + a single-station catalogue tunes it straight away; a multi-station one waits + for `enter`. - **A hero per catalogue** — the top-left art is the catalogue's own name in a shared half-block font: SomaFM in the spectrum gradient, tilderadio in the terminal's default colour, Bassdrive in its two blues. Every hero is three diff --git a/beefy_player/app.py b/beefy_player/app.py index 80290d0..8d20458 100644 --- a/beefy_player/app.py +++ b/beefy_player/app.py @@ -1035,7 +1035,13 @@ class BeefyPlayerApp(App[None]): self.query_one(HeaderBar).refresh() self.query_one(StagePanel).refresh_stage() self.query_one(StatusBar).refresh() - self._set_notice(f"catalogue: {provider.name}", 3) + + if self.audio_enabled and len(stations) == 1 and self.current is not None: + # A catalogue with one station has nothing to choose between: + # switching to it is the same as tuning it. + self._tune(self.current) + else: + self._set_notice(f"catalogue: {provider.name}", 3) if not self.offline: self._refresh_stations() diff --git a/tests/test_providers.py b/tests/test_providers.py index 0188d15..d004a70 100644 --- a/tests/test_providers.py +++ b/tests/test_providers.py @@ -257,3 +257,72 @@ async def test_every_catalogue_header_is_the_same_height() -> None: assert header.styles.height.value == 4, slug lines = [line.plain for line in header.render().renderables] assert any("STOPPED" in line or "PLAYING" in line for line in lines), slug + + +class _StubPlayer: + """Just enough Player for a tune: records what mpv would have played.""" + + def __init__(self) -> None: + import queue + + self.events: queue.Queue = queue.Queue() + self.played: list[list[str]] = [] + self.volume: int | None = None + self.backend: str | None = None + + def play(self, urls) -> None: + self.played.append(list(urls)) + + def set_volume(self, value: int) -> None: + self.volume = value + + def stop(self) -> None: + pass + + def toggle_pause(self) -> None: + pass + + +async def _switch_to(app, pilot, slug: str) -> None: + slugs = available_providers() + steps = (slugs.index(slug) - slugs.index(app.provider.id)) % len(slugs) + await pilot.press("p") + await pilot.pause() + for _ in range(steps): + await pilot.press("down") + await pilot.press("enter") + await pilot.pause() + await pilot.pause() + + +@pytest.mark.asyncio +async def test_switching_to_a_single_station_catalogue_tunes_it() -> None: + app = _CatalogueApp("somafm").app + async with app.run_test(size=(110, 40)) as pilot: + await pilot.pause() + stub = _StubPlayer() + app.player = stub + app.audio_enabled = True + + await _switch_to(app, pilot, "tilderadio") + + assert app.provider.id == "tilderadio" + assert app.current is not None and app.current.id == "tilderadio" + assert stub.played, "a one-station catalogue should start playing" + assert app.playing_state != "stopped" + + +@pytest.mark.asyncio +async def test_switching_to_a_multi_station_catalogue_waits_for_enter() -> None: + app = _CatalogueApp("tilderadio").app + async with app.run_test(size=(110, 40)) as pilot: + await pilot.pause() + stub = _StubPlayer() + app.player = stub + app.audio_enabled = True + + await _switch_to(app, pilot, "somafm") + + assert app.provider.id == "somafm" + assert len(app.stations) > 1 + assert stub.played == [], "multi-station catalogues wait for enter"