Update Bang & Olufsen source list as availability changes (#129910)

pull/129970/head
Markus Jacobsen 2024-11-06 11:52:00 +01:00 committed by Franck Nijhof
parent bdc17621ee
commit 0c9f30364c
No known key found for this signature in database
GPG Key ID: D62583BA8AB11CA3
6 changed files with 70 additions and 25 deletions

View File

@ -21,41 +21,57 @@ class BangOlufsenSource:
name="Audio Streamer",
id="uriStreamer",
is_seekable=False,
is_enabled=True,
is_playable=True,
)
BLUETOOTH: Final[Source] = Source(
name="Bluetooth",
id="bluetooth",
is_seekable=False,
is_enabled=True,
is_playable=True,
)
CHROMECAST: Final[Source] = Source(
name="Chromecast built-in",
id="chromeCast",
is_seekable=False,
is_enabled=True,
is_playable=True,
)
LINE_IN: Final[Source] = Source(
name="Line-In",
id="lineIn",
is_seekable=False,
is_enabled=True,
is_playable=True,
)
SPDIF: Final[Source] = Source(
name="Optical",
id="spdif",
is_seekable=False,
is_enabled=True,
is_playable=True,
)
NET_RADIO: Final[Source] = Source(
name="B&O Radio",
id="netRadio",
is_seekable=False,
is_enabled=True,
is_playable=True,
)
DEEZER: Final[Source] = Source(
name="Deezer",
id="deezer",
is_seekable=True,
is_enabled=True,
is_playable=True,
)
TIDAL: Final[Source] = Source(
name="Tidal",
id="tidal",
is_seekable=True,
is_enabled=True,
is_playable=True,
)
@ -170,20 +186,6 @@ VALID_MEDIA_TYPES: Final[tuple] = (
MediaType.CHANNEL,
)
# Sources on the device that should not be selectable by the user
HIDDEN_SOURCE_IDS: Final[tuple] = (
"airPlay",
"bluetooth",
"chromeCast",
"generator",
"local",
"dlna",
"qplay",
"wpl",
"pl",
"beolink",
"usbIn",
)
# Fallback sources to use in case of API failure.
FALLBACK_SOURCES: Final[SourceArray] = SourceArray(
@ -191,7 +193,7 @@ FALLBACK_SOURCES: Final[SourceArray] = SourceArray(
Source(
id="uriStreamer",
is_enabled=True,
is_playable=False,
is_playable=True,
name="Audio Streamer",
type=SourceTypeEnum(value="uriStreamer"),
is_seekable=False,
@ -199,7 +201,7 @@ FALLBACK_SOURCES: Final[SourceArray] = SourceArray(
Source(
id="bluetooth",
is_enabled=True,
is_playable=False,
is_playable=True,
name="Bluetooth",
type=SourceTypeEnum(value="bluetooth"),
is_seekable=False,
@ -207,7 +209,7 @@ FALLBACK_SOURCES: Final[SourceArray] = SourceArray(
Source(
id="spotify",
is_enabled=True,
is_playable=False,
is_playable=True,
name="Spotify Connect",
type=SourceTypeEnum(value="spotify"),
is_seekable=True,

View File

@ -70,7 +70,6 @@ from .const import (
CONNECTION_STATUS,
DOMAIN,
FALLBACK_SOURCES,
HIDDEN_SOURCE_IDS,
VALID_MEDIA_TYPES,
BangOlufsenMediaType,
BangOlufsenSource,
@ -169,6 +168,7 @@ class BangOlufsenMediaPlayer(BangOlufsenEntity, MediaPlayerEntity):
WebsocketNotification.PLAYBACK_ERROR: self._async_update_playback_error,
WebsocketNotification.PLAYBACK_METADATA: self._async_update_playback_metadata_and_beolink,
WebsocketNotification.PLAYBACK_PROGRESS: self._async_update_playback_progress,
WebsocketNotification.PLAYBACK_SOURCE: self._async_update_sources,
WebsocketNotification.PLAYBACK_STATE: self._async_update_playback_state,
WebsocketNotification.REMOTE_MENU_CHANGED: self._async_update_sources,
WebsocketNotification.SOURCE_CHANGE: self._async_update_source_change,
@ -243,7 +243,7 @@ class BangOlufsenMediaPlayer(BangOlufsenEntity, MediaPlayerEntity):
if queue_settings.shuffle is not None:
self._attr_shuffle = queue_settings.shuffle
async def _async_update_sources(self) -> None:
async def _async_update_sources(self, _: Source | None = None) -> None:
"""Get sources for the specific product."""
# Audio sources
@ -270,10 +270,7 @@ class BangOlufsenMediaPlayer(BangOlufsenEntity, MediaPlayerEntity):
self._audio_sources = {
source.id: source.name
for source in cast(list[Source], sources.items)
if source.is_enabled
and source.id
and source.name
and source.id not in HIDDEN_SOURCE_IDS
if source.is_enabled and source.id and source.name and source.is_playable
}
# Some sources are not Beolink expandable, meaning that they can't be joined by

View File

@ -63,6 +63,9 @@ class BangOlufsenWebsocket(BangOlufsenBase):
self._client.get_playback_progress_notifications(
self.on_playback_progress_notification
)
self._client.get_playback_source_notifications(
self.on_playback_source_notification
)
self._client.get_playback_state_notifications(
self.on_playback_state_notification
)
@ -157,6 +160,14 @@ class BangOlufsenWebsocket(BangOlufsenBase):
notification,
)
def on_playback_source_notification(self, notification: Source) -> None:
"""Send playback_source dispatch."""
async_dispatcher_send(
self.hass,
f"{self._unique_id}_{WebsocketNotification.PLAYBACK_SOURCE}",
notification,
)
def on_source_change_notification(self, notification: Source) -> None:
"""Send source_change dispatch."""
async_dispatcher_send(

View File

@ -124,7 +124,7 @@ def mock_mozart_client() -> Generator[AsyncMock]:
client.get_available_sources = AsyncMock()
client.get_available_sources.return_value = SourceArray(
items=[
# Is in the HIDDEN_SOURCE_IDS constant, so should not be user selectable
# Is not playable, so should not be user selectable
Source(
name="AirPlay",
id="airPlay",
@ -137,14 +137,16 @@ def mock_mozart_client() -> Generator[AsyncMock]:
id="tidal",
is_enabled=True,
is_multiroom_available=True,
is_playable=True,
),
Source(
name="Line-In",
id="lineIn",
is_enabled=True,
is_multiroom_available=False,
is_playable=True,
),
# Is disabled, so should not be user selectable
# Is disabled and not playable, so should not be user selectable
Source(
name="Powerlink",
id="pl",

View File

@ -130,6 +130,7 @@ TEST_VIDEO_SOURCES = ["HDMI A"]
TEST_SOURCES = TEST_AUDIO_SOURCES + TEST_VIDEO_SOURCES
TEST_FALLBACK_SOURCES = [
"Audio Streamer",
"Bluetooth",
"Spotify Connect",
"Line-In",
"Optical",

View File

@ -10,6 +10,7 @@ from mozart_api.models import (
PlayQueueSettings,
RenderingState,
Source,
SourceArray,
WebsocketNotificationTag,
)
import pytest
@ -195,6 +196,37 @@ async def test_async_update_sources_remote(
assert mock_mozart_client.get_remote_menu.call_count == 2
async def test_async_update_sources_availability(
hass: HomeAssistant,
mock_mozart_client: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test that the playback_source WebSocket event updates available playback sources."""
# Remove video sources to simplify test
mock_mozart_client.get_remote_menu.return_value = {}
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
playback_source_callback = (
mock_mozart_client.get_playback_source_notifications.call_args[0][0]
)
assert mock_mozart_client.get_available_sources.call_count == 1
# Add a source that is available and playable
mock_mozart_client.get_available_sources.return_value = SourceArray(
items=[BangOlufsenSource.TIDAL]
)
# Send playback_source. The source is not actually used, so its attributes don't matter
playback_source_callback(Source())
assert mock_mozart_client.get_available_sources.call_count == 2
assert (states := hass.states.get(TEST_MEDIA_PLAYER_ENTITY_ID))
assert states.attributes[ATTR_INPUT_SOURCE_LIST] == [BangOlufsenSource.TIDAL.name]
async def test_async_update_playback_metadata(
hass: HomeAssistant,
mock_mozart_client: AsyncMock,