155 lines
4.3 KiB
Python
155 lines
4.3 KiB
Python
"""Support for media browsing."""
|
|
import logging
|
|
|
|
from afsapi import AFSAPI, FSApiException, OutOfRangeException, Preset
|
|
|
|
from homeassistant.components.media_player import (
|
|
BrowseError,
|
|
BrowseMedia,
|
|
MediaClass,
|
|
MediaType,
|
|
)
|
|
|
|
from .const import MEDIA_CONTENT_ID_CHANNELS, MEDIA_CONTENT_ID_PRESET
|
|
|
|
TOP_LEVEL_DIRECTORIES = {
|
|
MEDIA_CONTENT_ID_CHANNELS: "Channels",
|
|
MEDIA_CONTENT_ID_PRESET: "Presets",
|
|
}
|
|
|
|
FSAPI_ITEM_TYPE_TO_MEDIA_CLASS = {
|
|
0: MediaClass.DIRECTORY,
|
|
1: MediaClass.CHANNEL,
|
|
2: MediaClass.CHANNEL,
|
|
}
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
def _item_preset_payload(preset: Preset, player_mode: str) -> BrowseMedia:
|
|
"""Create response payload for a single media item.
|
|
|
|
Used by async_browse_media.
|
|
"""
|
|
return BrowseMedia(
|
|
title=preset.name,
|
|
media_class=MediaClass.CHANNEL,
|
|
media_content_type=MediaType.CHANNEL,
|
|
# We add 1 to the preset key to keep it in sync with the numbering shown
|
|
# on the interface of the device
|
|
media_content_id=f"{player_mode}/{MEDIA_CONTENT_ID_PRESET}/{int(preset.key)+1}",
|
|
can_play=True,
|
|
can_expand=False,
|
|
)
|
|
|
|
|
|
def _item_payload(
|
|
key, item: dict[str, str], player_mode: str, parent_keys: list[str]
|
|
) -> BrowseMedia:
|
|
"""Create response payload for a single media item.
|
|
|
|
Used by async_browse_media.
|
|
"""
|
|
assert "label" in item or "name" in item
|
|
assert "type" in item
|
|
|
|
title = item.get("label") or item.get("name") or "Unknown"
|
|
title = title.strip()
|
|
|
|
media_content_id = "/".join(
|
|
[player_mode, MEDIA_CONTENT_ID_CHANNELS, *parent_keys, key]
|
|
)
|
|
media_class = (
|
|
FSAPI_ITEM_TYPE_TO_MEDIA_CLASS.get(int(item["type"])) or MediaClass.CHANNEL
|
|
)
|
|
|
|
return BrowseMedia(
|
|
title=title,
|
|
media_class=media_class,
|
|
media_content_type=MediaClass.CHANNEL,
|
|
media_content_id=media_content_id,
|
|
can_play=(media_class != MediaClass.DIRECTORY),
|
|
can_expand=(media_class == MediaClass.DIRECTORY),
|
|
)
|
|
|
|
|
|
async def browse_top_level(current_mode, afsapi: AFSAPI):
|
|
"""Create response payload to describe contents of a specific library.
|
|
|
|
Used by async_browse_media.
|
|
"""
|
|
|
|
children = [
|
|
BrowseMedia(
|
|
title=name,
|
|
media_class=MediaClass.DIRECTORY,
|
|
media_content_type=MediaType.CHANNELS,
|
|
media_content_id=(
|
|
f"{current_mode or 'unknown'}/{top_level_media_content_id}"
|
|
),
|
|
can_play=False,
|
|
can_expand=True,
|
|
)
|
|
for top_level_media_content_id, name in TOP_LEVEL_DIRECTORIES.items()
|
|
]
|
|
|
|
library_info = BrowseMedia(
|
|
media_class=MediaClass.DIRECTORY,
|
|
media_content_id="library",
|
|
media_content_type=MediaType.CHANNELS,
|
|
title="Media Library",
|
|
can_play=False,
|
|
can_expand=True,
|
|
children=children,
|
|
children_media_class=MediaClass.DIRECTORY,
|
|
)
|
|
|
|
return library_info
|
|
|
|
|
|
async def browse_node(
|
|
afsapi: AFSAPI,
|
|
media_content_type,
|
|
media_content_id,
|
|
):
|
|
"""List the contents of a navigation directory (or preset list)."""
|
|
|
|
player_mode, browse_type, *parent_keys = media_content_id.split("/")
|
|
|
|
title = TOP_LEVEL_DIRECTORIES.get(browse_type, "Unknown")
|
|
|
|
children = []
|
|
try:
|
|
if browse_type == MEDIA_CONTENT_ID_PRESET:
|
|
# Return the presets
|
|
|
|
children = [
|
|
_item_preset_payload(preset, player_mode=player_mode)
|
|
for preset in await afsapi.get_presets()
|
|
]
|
|
|
|
else:
|
|
# Browse to correct folder
|
|
await afsapi.nav_select_folder_via_path(parent_keys)
|
|
|
|
# Return items in this folder
|
|
children = [
|
|
_item_payload(key, item, player_mode, parent_keys=parent_keys)
|
|
async for key, item in await afsapi.nav_list()
|
|
]
|
|
except OutOfRangeException as err:
|
|
raise BrowseError("The requested item is out of range") from err
|
|
except FSApiException as err:
|
|
raise BrowseError(str(err)) from err
|
|
|
|
return BrowseMedia(
|
|
title=title,
|
|
media_content_id=media_content_id,
|
|
media_content_type=MediaType.CHANNELS,
|
|
media_class=MediaClass.DIRECTORY,
|
|
can_play=False,
|
|
can_expand=True,
|
|
children=children,
|
|
children_media_class=MediaType.CHANNEL,
|
|
)
|