feat(stage): show Bandcamp link in ON AIR
This commit is contained in:
1 parent
8e1c0a64eb
commit
66729c7542
4 files changed
+81
-3
No files matched your search
@@ -56,6 +56,7 @@ class Song:
|
||||
artist: str = ""
|
||||
album: str = ""
|
||||
art_url: str = ""
|
||||
bandcamp_url: str = ""
|
||||
played_at: datetime | None = None
|
||||
|
||||
@property
|
||||
|
||||
+46
-1
@@ -17,7 +17,7 @@ from textual.css.query import NoMatches
|
||||
from textual.theme import Theme
|
||||
from textual.widgets import Input
|
||||
|
||||
from . import api, artwork, config
|
||||
from . import api, artwork, bandcamp, config
|
||||
from .api import Station
|
||||
from .fallback import fallback_channels
|
||||
from .mpris import MprisService
|
||||
@@ -133,6 +133,7 @@ class SomaFMApp(App[None]):
|
||||
self.artwork_url = ""
|
||||
self.artwork_bytes: bytes | None = None
|
||||
self._art_track_key = ""
|
||||
self._bandcamp_key = ""
|
||||
self._http: httpx.AsyncClient | None = None
|
||||
self._closed = False
|
||||
self._tick_timer = None
|
||||
@@ -415,8 +416,51 @@ class SomaFMApp(App[None]):
|
||||
)
|
||||
self.mpris.set_status(self._mpris_status())
|
||||
self.mpris.set_volume(self.volume / 100.0)
|
||||
self._update_bandcamp()
|
||||
self._update_artwork()
|
||||
|
||||
def _update_bandcamp(self) -> None:
|
||||
"""Find a Bandcamp track link once per now-playing track."""
|
||||
station = self.current
|
||||
if station is None or self.offline or self._http is None:
|
||||
return
|
||||
song = station.now_playing
|
||||
if song is None or not (song.artist.strip() or song.title.strip()):
|
||||
return
|
||||
|
||||
track_key = f"{station.id}|{song.key()}"
|
||||
if track_key == self._bandcamp_key:
|
||||
return
|
||||
self._bandcamp_key = track_key
|
||||
self._fetch_bandcamp(
|
||||
station.id, song.key(), song.artist, song.album, song.title
|
||||
)
|
||||
|
||||
@work(exclusive=True, group="bandcamp", exit_on_error=False)
|
||||
async def _fetch_bandcamp(
|
||||
self,
|
||||
station_id: str,
|
||||
song_key: str,
|
||||
artist: str,
|
||||
album: str,
|
||||
title: str,
|
||||
) -> None:
|
||||
if self._http is None or self.offline:
|
||||
return
|
||||
url = await bandcamp.find_track(self._http, artist, album, title)
|
||||
if not url:
|
||||
return
|
||||
|
||||
station = next((s for s in self.stations if s.id == station_id), None)
|
||||
if station is None or self.current is None or self.current.id != station_id:
|
||||
return
|
||||
song = station.now_playing
|
||||
if song is None or song.key() != song_key:
|
||||
return
|
||||
song.bandcamp_url = url
|
||||
with contextlib.suppress(NoMatches):
|
||||
self.query_one(StagePanel).refresh_stage()
|
||||
|
||||
def _refresh_art(self) -> None:
|
||||
with contextlib.suppress(NoMatches):
|
||||
self.query_one(StagePanel).refresh_art()
|
||||
@@ -758,6 +802,7 @@ class SomaFMApp(App[None]):
|
||||
|
||||
def action_refresh(self) -> None:
|
||||
self._last_history_fetch = 0.0
|
||||
self._bandcamp_key = ""
|
||||
self._set_notice("refreshing…", 2)
|
||||
if self.offline:
|
||||
self._set_notice("offline mode — nothing to refresh", 3)
|
||||
|
||||
@@ -161,7 +161,7 @@ class StageInfo(Widget):
|
||||
|
||||
notice = Text(truncate(app.notice, width), style="italic yellow")
|
||||
|
||||
return [
|
||||
details = [
|
||||
heading,
|
||||
description,
|
||||
Text(""),
|
||||
@@ -170,8 +170,16 @@ class StageInfo(Widget):
|
||||
title,
|
||||
album,
|
||||
meta,
|
||||
notice,
|
||||
]
|
||||
if song is not None and song.bandcamp_url:
|
||||
link = Text(no_wrap=True)
|
||||
link.append(
|
||||
"Bandcamp ↗",
|
||||
style=f"bold cyan link {song.bandcamp_url}",
|
||||
)
|
||||
details.append(link)
|
||||
details.append(notice)
|
||||
return details
|
||||
|
||||
|
||||
class HistoryPane(Widget):
|
||||
|
||||
@@ -133,3 +133,27 @@ async def test_narrow_layout() -> None:
|
||||
text = rendered_text(app)
|
||||
assert "STATIONS" in text
|
||||
assert "ON AIR" in text
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_stage_shows_bandcamp_link_only_when_present() -> None:
|
||||
app = SomaFMApp(autoplay=False, offline=True, audio=False)
|
||||
async with app.run_test(size=(110, 40)) as pilot:
|
||||
await pilot.pause()
|
||||
station = app.current
|
||||
assert station is not None
|
||||
song = Song(
|
||||
title="Rauschebart",
|
||||
artist="Jens Buchert",
|
||||
album="Gravity",
|
||||
bandcamp_url="https://jensbuchert.bandcamp.com/track/rauschebart",
|
||||
)
|
||||
station.now_playing = song
|
||||
app.query_one(StagePanel).refresh_stage()
|
||||
await pilot.pause()
|
||||
assert "Bandcamp" in rendered_text(app)
|
||||
|
||||
song.bandcamp_url = ""
|
||||
app.query_one(StagePanel).refresh_stage()
|
||||
await pilot.pause()
|
||||
assert "Bandcamp" not in rendered_text(app)
|
||||
Reference in new issue
Block a user