Add guard radio browser media source (#67486)

pull/67488/head
Paulus Schoutsen 2022-03-02 08:22:34 -08:00 committed by GitHub
parent a97fe7aae0
commit dc24e6505e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 44 additions and 30 deletions

View File

@ -12,6 +12,7 @@ from homeassistant.components.media_player.const import (
MEDIA_TYPE_MUSIC, MEDIA_TYPE_MUSIC,
) )
from homeassistant.components.media_player.errors import BrowseError from homeassistant.components.media_player.errors import BrowseError
from homeassistant.components.media_source.error import Unresolvable
from homeassistant.components.media_source.models import ( from homeassistant.components.media_source.models import (
BrowseMediaSource, BrowseMediaSource,
MediaSource, MediaSource,
@ -35,9 +36,8 @@ async def async_get_media_source(hass: HomeAssistant) -> RadioMediaSource:
"""Set up Radio Browser media source.""" """Set up Radio Browser media source."""
# Radio browser support only a single config entry # Radio browser support only a single config entry
entry = hass.config_entries.async_entries(DOMAIN)[0] entry = hass.config_entries.async_entries(DOMAIN)[0]
radios = hass.data[DOMAIN]
return RadioMediaSource(hass, radios, entry) return RadioMediaSource(hass, entry)
class RadioMediaSource(MediaSource): class RadioMediaSource(MediaSource):
@ -45,26 +45,33 @@ class RadioMediaSource(MediaSource):
name = "Radio Browser" name = "Radio Browser"
def __init__( def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None:
self, hass: HomeAssistant, radios: RadioBrowser, entry: ConfigEntry
) -> None:
"""Initialize CameraMediaSource.""" """Initialize CameraMediaSource."""
super().__init__(DOMAIN) super().__init__(DOMAIN)
self.hass = hass self.hass = hass
self.entry = entry self.entry = entry
self.radios = radios
@property
def radios(self) -> RadioBrowser | None:
"""Return the radio browser."""
return self.hass.data.get(DOMAIN)
async def async_resolve_media(self, item: MediaSourceItem) -> PlayMedia: async def async_resolve_media(self, item: MediaSourceItem) -> PlayMedia:
"""Resolve selected Radio station to a streaming URL.""" """Resolve selected Radio station to a streaming URL."""
station = await self.radios.station(uuid=item.identifier) radios = self.radios
if radios is None:
raise Unresolvable("Radio Browser not initialized")
station = await radios.station(uuid=item.identifier)
if not station: if not station:
raise BrowseError("Radio station is no longer available") raise Unresolvable("Radio station is no longer available")
if not (mime_type := self._async_get_station_mime_type(station)): if not (mime_type := self._async_get_station_mime_type(station)):
raise BrowseError("Could not determine stream type of radio station") raise Unresolvable("Could not determine stream type of radio station")
# Register "click" with Radio Browser # Register "click" with Radio Browser
await self.radios.station_click(uuid=station.uuid) await radios.station_click(uuid=station.uuid)
return PlayMedia(station.url, mime_type) return PlayMedia(station.url, mime_type)
@ -73,6 +80,11 @@ class RadioMediaSource(MediaSource):
item: MediaSourceItem, item: MediaSourceItem,
) -> BrowseMediaSource: ) -> BrowseMediaSource:
"""Return media.""" """Return media."""
radios = self.radios
if radios is None:
raise BrowseError("Radio Browser not initialized")
return BrowseMediaSource( return BrowseMediaSource(
domain=DOMAIN, domain=DOMAIN,
identifier=None, identifier=None,
@ -83,10 +95,10 @@ class RadioMediaSource(MediaSource):
can_expand=True, can_expand=True,
children_media_class=MEDIA_CLASS_DIRECTORY, children_media_class=MEDIA_CLASS_DIRECTORY,
children=[ children=[
*await self._async_build_popular(item), *await self._async_build_popular(radios, item),
*await self._async_build_by_tag(item), *await self._async_build_by_tag(radios, item),
*await self._async_build_by_language(item), *await self._async_build_by_language(radios, item),
*await self._async_build_by_country(item), *await self._async_build_by_country(radios, item),
], ],
) )
@ -100,7 +112,9 @@ class RadioMediaSource(MediaSource):
return mime_type return mime_type
@callback @callback
def _async_build_stations(self, stations: list[Station]) -> list[BrowseMediaSource]: def _async_build_stations(
self, radios: RadioBrowser, stations: list[Station]
) -> list[BrowseMediaSource]:
"""Build list of media sources from radio stations.""" """Build list of media sources from radio stations."""
items: list[BrowseMediaSource] = [] items: list[BrowseMediaSource] = []
@ -126,23 +140,23 @@ class RadioMediaSource(MediaSource):
return items return items
async def _async_build_by_country( async def _async_build_by_country(
self, item: MediaSourceItem self, radios: RadioBrowser, item: MediaSourceItem
) -> list[BrowseMediaSource]: ) -> list[BrowseMediaSource]:
"""Handle browsing radio stations by country.""" """Handle browsing radio stations by country."""
category, _, country_code = (item.identifier or "").partition("/") category, _, country_code = (item.identifier or "").partition("/")
if country_code: if country_code:
stations = await self.radios.stations( stations = await radios.stations(
filter_by=FilterBy.COUNTRY_CODE_EXACT, filter_by=FilterBy.COUNTRY_CODE_EXACT,
filter_term=country_code, filter_term=country_code,
hide_broken=True, hide_broken=True,
order=Order.NAME, order=Order.NAME,
reverse=False, reverse=False,
) )
return self._async_build_stations(stations) return self._async_build_stations(radios, stations)
# We show country in the root additionally, when there is no item # We show country in the root additionally, when there is no item
if not item.identifier or category == "country": if not item.identifier or category == "country":
countries = await self.radios.countries(order=Order.NAME) countries = await radios.countries(order=Order.NAME)
return [ return [
BrowseMediaSource( BrowseMediaSource(
domain=DOMAIN, domain=DOMAIN,
@ -160,22 +174,22 @@ class RadioMediaSource(MediaSource):
return [] return []
async def _async_build_by_language( async def _async_build_by_language(
self, item: MediaSourceItem self, radios: RadioBrowser, item: MediaSourceItem
) -> list[BrowseMediaSource]: ) -> list[BrowseMediaSource]:
"""Handle browsing radio stations by language.""" """Handle browsing radio stations by language."""
category, _, language = (item.identifier or "").partition("/") category, _, language = (item.identifier or "").partition("/")
if category == "language" and language: if category == "language" and language:
stations = await self.radios.stations( stations = await radios.stations(
filter_by=FilterBy.LANGUAGE_EXACT, filter_by=FilterBy.LANGUAGE_EXACT,
filter_term=language, filter_term=language,
hide_broken=True, hide_broken=True,
order=Order.NAME, order=Order.NAME,
reverse=False, reverse=False,
) )
return self._async_build_stations(stations) return self._async_build_stations(radios, stations)
if category == "language": if category == "language":
languages = await self.radios.languages(order=Order.NAME, hide_broken=True) languages = await radios.languages(order=Order.NAME, hide_broken=True)
return [ return [
BrowseMediaSource( BrowseMediaSource(
domain=DOMAIN, domain=DOMAIN,
@ -206,17 +220,17 @@ class RadioMediaSource(MediaSource):
return [] return []
async def _async_build_popular( async def _async_build_popular(
self, item: MediaSourceItem self, radios: RadioBrowser, item: MediaSourceItem
) -> list[BrowseMediaSource]: ) -> list[BrowseMediaSource]:
"""Handle browsing popular radio stations.""" """Handle browsing popular radio stations."""
if item.identifier == "popular": if item.identifier == "popular":
stations = await self.radios.stations( stations = await radios.stations(
hide_broken=True, hide_broken=True,
limit=250, limit=250,
order=Order.CLICK_COUNT, order=Order.CLICK_COUNT,
reverse=True, reverse=True,
) )
return self._async_build_stations(stations) return self._async_build_stations(radios, stations)
if not item.identifier: if not item.identifier:
return [ return [
@ -234,22 +248,22 @@ class RadioMediaSource(MediaSource):
return [] return []
async def _async_build_by_tag( async def _async_build_by_tag(
self, item: MediaSourceItem self, radios: RadioBrowser, item: MediaSourceItem
) -> list[BrowseMediaSource]: ) -> list[BrowseMediaSource]:
"""Handle browsing radio stations by tags.""" """Handle browsing radio stations by tags."""
category, _, tag = (item.identifier or "").partition("/") category, _, tag = (item.identifier or "").partition("/")
if category == "tag" and tag: if category == "tag" and tag:
stations = await self.radios.stations( stations = await radios.stations(
filter_by=FilterBy.TAG_EXACT, filter_by=FilterBy.TAG_EXACT,
filter_term=tag, filter_term=tag,
hide_broken=True, hide_broken=True,
order=Order.NAME, order=Order.NAME,
reverse=False, reverse=False,
) )
return self._async_build_stations(stations) return self._async_build_stations(radios, stations)
if category == "tag": if category == "tag":
tags = await self.radios.tags( tags = await radios.tags(
hide_broken=True, hide_broken=True,
limit=100, limit=100,
order=Order.STATION_COUNT, order=Order.STATION_COUNT,