feat(catalogue): tune a single-station catalogue on switch

This commit is contained in:
beefcarver committed 2026-11-03 20:31:18 -05:00
1 parent 0219df454c
commit 5eb60d378c
3 files changed
+79 -2

No files matched your search

+3 -1
View File
@@ -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
+7 -1
View File
@@ -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()
+69
View File
@@ -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"