Add media browsing to Cambridge Audio (#129106)

* Add media browsing to Cambridge Audio

* Remove one folder logic

* Remove class mapping for presets
This commit is contained in:
Noah Husby 2024-12-22 15:05:07 -05:00 committed by GitHub
parent 0ad9af0feb
commit 26180486e7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 201 additions and 4 deletions

View File

@ -0,0 +1,85 @@
"""Support for media browsing."""
from aiostreammagic import StreamMagicClient
from aiostreammagic.models import Preset
from homeassistant.components.media_player import BrowseMedia, MediaClass
from homeassistant.core import HomeAssistant
async def async_browse_media(
hass: HomeAssistant,
client: StreamMagicClient,
media_content_id: str | None,
media_content_type: str | None,
) -> BrowseMedia:
"""Browse media."""
if media_content_type == "presets":
return await _presets_payload(client.preset_list.presets)
return await _root_payload(
hass,
client,
)
async def _root_payload(
hass: HomeAssistant,
client: StreamMagicClient,
) -> BrowseMedia:
"""Return root payload for Cambridge Audio."""
children: list[BrowseMedia] = []
if client.preset_list.presets:
children.append(
BrowseMedia(
title="Presets",
media_class=MediaClass.DIRECTORY,
media_content_id="",
media_content_type="presets",
thumbnail="https://brands.home-assistant.io/_/cambridge_audio/logo.png",
can_play=False,
can_expand=True,
)
)
return BrowseMedia(
title="Cambridge Audio",
media_class=MediaClass.DIRECTORY,
media_content_id="",
media_content_type="root",
can_play=False,
can_expand=True,
children=children,
)
async def _presets_payload(presets: list[Preset]) -> BrowseMedia:
"""Create payload to list presets."""
children: list[BrowseMedia] = []
for preset in presets:
if preset.state != "OK":
continue
children.append(
BrowseMedia(
title=preset.name,
media_class=MediaClass.MUSIC,
media_content_id=str(preset.preset_id),
media_content_type="preset",
can_play=True,
can_expand=False,
thumbnail=preset.art_url,
)
)
return BrowseMedia(
title="Presets",
media_class=MediaClass.DIRECTORY,
media_content_id="",
media_content_type="presets",
can_play=False,
can_expand=True,
children=children,
)

View File

@ -13,6 +13,7 @@ from aiostreammagic import (
)
from homeassistant.components.media_player import (
BrowseMedia,
MediaPlayerDeviceClass,
MediaPlayerEntity,
MediaPlayerEntityFeature,
@ -24,7 +25,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import CambridgeAudioConfigEntry
from . import CambridgeAudioConfigEntry, media_browser
from .const import (
CAMBRIDGE_MEDIA_TYPE_AIRABLE,
CAMBRIDGE_MEDIA_TYPE_INTERNET_RADIO,
@ -34,7 +35,8 @@ from .const import (
from .entity import CambridgeAudioEntity, command
BASE_FEATURES = (
MediaPlayerEntityFeature.SELECT_SOURCE
MediaPlayerEntityFeature.BROWSE_MEDIA
| MediaPlayerEntityFeature.SELECT_SOURCE
| MediaPlayerEntityFeature.TURN_OFF
| MediaPlayerEntityFeature.TURN_ON
| MediaPlayerEntityFeature.PLAY_MEDIA
@ -338,3 +340,13 @@ class CambridgeAudioDevice(CambridgeAudioEntity, MediaPlayerEntity):
if media_type == CAMBRIDGE_MEDIA_TYPE_INTERNET_RADIO:
await self.client.play_radio_url("Radio", media_id)
async def async_browse_media(
self,
media_content_type: MediaType | str | None = None,
media_content_id: str | None = None,
) -> BrowseMedia:
"""Implement the media browsing helper."""
return await media_browser.async_browse_media(
self.hass, self.client, media_content_id, media_content_type
)

View File

@ -28,7 +28,7 @@
"name": "Unknown Preset Type",
"type": "Unknown",
"class": "stream.unknown",
"state": "OK"
"state": "UNAVAILABLE"
}
]
}

View File

@ -78,7 +78,7 @@
'name': 'Unknown Preset Type',
'preset_class': 'stream.unknown',
'preset_id': 3,
'state': 'OK',
'state': 'UNAVAILABLE',
'type': 'Unknown',
}),
]),

View File

@ -0,0 +1,39 @@
# serializer version: 1
# name: test_browse_media_root
list([
dict({
'can_expand': True,
'can_play': False,
'children_media_class': None,
'media_class': 'directory',
'media_content_id': '',
'media_content_type': 'presets',
'thumbnail': 'https://brands.home-assistant.io/_/cambridge_audio/logo.png',
'title': 'Presets',
}),
])
# ---
# name: test_browse_presets
list([
dict({
'can_expand': False,
'can_play': True,
'children_media_class': None,
'media_class': 'music',
'media_content_id': '1',
'media_content_type': 'preset',
'thumbnail': 'https://static.airable.io/43/68/432868.png',
'title': 'Chicago House Radio',
}),
dict({
'can_expand': False,
'can_play': True,
'children_media_class': None,
'media_class': 'music',
'media_content_id': '2',
'media_content_type': 'preset',
'thumbnail': 'https://i.scdn.co/image/ab67616d0000b27325a5a1ed28871e8e53e62d59',
'title': 'Spotify: Good & Evil',
}),
])
# ---

View File

@ -0,0 +1,61 @@
"""Tests for the Cambridge Audio media browser."""
from unittest.mock import AsyncMock
from syrupy import SnapshotAssertion
from homeassistant.core import HomeAssistant
from . import setup_integration
from .const import ENTITY_ID
from tests.common import MockConfigEntry
from tests.typing import WebSocketGenerator
async def test_browse_media_root(
hass: HomeAssistant,
mock_stream_magic_client: AsyncMock,
mock_config_entry: MockConfigEntry,
hass_ws_client: WebSocketGenerator,
snapshot: SnapshotAssertion,
) -> None:
"""Test the root browse page."""
await setup_integration(hass, mock_config_entry)
client = await hass_ws_client()
await client.send_json(
{
"id": 1,
"type": "media_player/browse_media",
"entity_id": ENTITY_ID,
}
)
response = await client.receive_json()
assert response["success"]
assert response["result"]["children"] == snapshot
async def test_browse_presets(
hass: HomeAssistant,
mock_stream_magic_client: AsyncMock,
mock_config_entry: MockConfigEntry,
hass_ws_client: WebSocketGenerator,
snapshot: SnapshotAssertion,
) -> None:
"""Test the presets browse page."""
await setup_integration(hass, mock_config_entry)
client = await hass_ws_client()
await client.send_json(
{
"id": 1,
"type": "media_player/browse_media",
"entity_id": ENTITY_ID,
"media_content_type": "presets",
"media_content_id": "",
}
)
response = await client.receive_json()
assert response["success"]
assert response["result"]["children"] == snapshot