Update Bang & Olufsen source list as availability changes (#129910)
parent
bdc17621ee
commit
0c9f30364c
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue