Update Bang & Olufsen source list as availability changes (#129910)
parent
bdc17621ee
commit
0c9f30364c
|
@ -21,41 +21,57 @@ class BangOlufsenSource:
|
||||||
name="Audio Streamer",
|
name="Audio Streamer",
|
||||||
id="uriStreamer",
|
id="uriStreamer",
|
||||||
is_seekable=False,
|
is_seekable=False,
|
||||||
|
is_enabled=True,
|
||||||
|
is_playable=True,
|
||||||
)
|
)
|
||||||
BLUETOOTH: Final[Source] = Source(
|
BLUETOOTH: Final[Source] = Source(
|
||||||
name="Bluetooth",
|
name="Bluetooth",
|
||||||
id="bluetooth",
|
id="bluetooth",
|
||||||
is_seekable=False,
|
is_seekable=False,
|
||||||
|
is_enabled=True,
|
||||||
|
is_playable=True,
|
||||||
)
|
)
|
||||||
CHROMECAST: Final[Source] = Source(
|
CHROMECAST: Final[Source] = Source(
|
||||||
name="Chromecast built-in",
|
name="Chromecast built-in",
|
||||||
id="chromeCast",
|
id="chromeCast",
|
||||||
is_seekable=False,
|
is_seekable=False,
|
||||||
|
is_enabled=True,
|
||||||
|
is_playable=True,
|
||||||
)
|
)
|
||||||
LINE_IN: Final[Source] = Source(
|
LINE_IN: Final[Source] = Source(
|
||||||
name="Line-In",
|
name="Line-In",
|
||||||
id="lineIn",
|
id="lineIn",
|
||||||
is_seekable=False,
|
is_seekable=False,
|
||||||
|
is_enabled=True,
|
||||||
|
is_playable=True,
|
||||||
)
|
)
|
||||||
SPDIF: Final[Source] = Source(
|
SPDIF: Final[Source] = Source(
|
||||||
name="Optical",
|
name="Optical",
|
||||||
id="spdif",
|
id="spdif",
|
||||||
is_seekable=False,
|
is_seekable=False,
|
||||||
|
is_enabled=True,
|
||||||
|
is_playable=True,
|
||||||
)
|
)
|
||||||
NET_RADIO: Final[Source] = Source(
|
NET_RADIO: Final[Source] = Source(
|
||||||
name="B&O Radio",
|
name="B&O Radio",
|
||||||
id="netRadio",
|
id="netRadio",
|
||||||
is_seekable=False,
|
is_seekable=False,
|
||||||
|
is_enabled=True,
|
||||||
|
is_playable=True,
|
||||||
)
|
)
|
||||||
DEEZER: Final[Source] = Source(
|
DEEZER: Final[Source] = Source(
|
||||||
name="Deezer",
|
name="Deezer",
|
||||||
id="deezer",
|
id="deezer",
|
||||||
is_seekable=True,
|
is_seekable=True,
|
||||||
|
is_enabled=True,
|
||||||
|
is_playable=True,
|
||||||
)
|
)
|
||||||
TIDAL: Final[Source] = Source(
|
TIDAL: Final[Source] = Source(
|
||||||
name="Tidal",
|
name="Tidal",
|
||||||
id="tidal",
|
id="tidal",
|
||||||
is_seekable=True,
|
is_seekable=True,
|
||||||
|
is_enabled=True,
|
||||||
|
is_playable=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -170,20 +186,6 @@ VALID_MEDIA_TYPES: Final[tuple] = (
|
||||||
MediaType.CHANNEL,
|
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 to use in case of API failure.
|
||||||
FALLBACK_SOURCES: Final[SourceArray] = SourceArray(
|
FALLBACK_SOURCES: Final[SourceArray] = SourceArray(
|
||||||
|
@ -191,7 +193,7 @@ FALLBACK_SOURCES: Final[SourceArray] = SourceArray(
|
||||||
Source(
|
Source(
|
||||||
id="uriStreamer",
|
id="uriStreamer",
|
||||||
is_enabled=True,
|
is_enabled=True,
|
||||||
is_playable=False,
|
is_playable=True,
|
||||||
name="Audio Streamer",
|
name="Audio Streamer",
|
||||||
type=SourceTypeEnum(value="uriStreamer"),
|
type=SourceTypeEnum(value="uriStreamer"),
|
||||||
is_seekable=False,
|
is_seekable=False,
|
||||||
|
@ -199,7 +201,7 @@ FALLBACK_SOURCES: Final[SourceArray] = SourceArray(
|
||||||
Source(
|
Source(
|
||||||
id="bluetooth",
|
id="bluetooth",
|
||||||
is_enabled=True,
|
is_enabled=True,
|
||||||
is_playable=False,
|
is_playable=True,
|
||||||
name="Bluetooth",
|
name="Bluetooth",
|
||||||
type=SourceTypeEnum(value="bluetooth"),
|
type=SourceTypeEnum(value="bluetooth"),
|
||||||
is_seekable=False,
|
is_seekable=False,
|
||||||
|
@ -207,7 +209,7 @@ FALLBACK_SOURCES: Final[SourceArray] = SourceArray(
|
||||||
Source(
|
Source(
|
||||||
id="spotify",
|
id="spotify",
|
||||||
is_enabled=True,
|
is_enabled=True,
|
||||||
is_playable=False,
|
is_playable=True,
|
||||||
name="Spotify Connect",
|
name="Spotify Connect",
|
||||||
type=SourceTypeEnum(value="spotify"),
|
type=SourceTypeEnum(value="spotify"),
|
||||||
is_seekable=True,
|
is_seekable=True,
|
||||||
|
|
|
@ -70,7 +70,6 @@ from .const import (
|
||||||
CONNECTION_STATUS,
|
CONNECTION_STATUS,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
FALLBACK_SOURCES,
|
FALLBACK_SOURCES,
|
||||||
HIDDEN_SOURCE_IDS,
|
|
||||||
VALID_MEDIA_TYPES,
|
VALID_MEDIA_TYPES,
|
||||||
BangOlufsenMediaType,
|
BangOlufsenMediaType,
|
||||||
BangOlufsenSource,
|
BangOlufsenSource,
|
||||||
|
@ -169,6 +168,7 @@ class BangOlufsenMediaPlayer(BangOlufsenEntity, MediaPlayerEntity):
|
||||||
WebsocketNotification.PLAYBACK_ERROR: self._async_update_playback_error,
|
WebsocketNotification.PLAYBACK_ERROR: self._async_update_playback_error,
|
||||||
WebsocketNotification.PLAYBACK_METADATA: self._async_update_playback_metadata_and_beolink,
|
WebsocketNotification.PLAYBACK_METADATA: self._async_update_playback_metadata_and_beolink,
|
||||||
WebsocketNotification.PLAYBACK_PROGRESS: self._async_update_playback_progress,
|
WebsocketNotification.PLAYBACK_PROGRESS: self._async_update_playback_progress,
|
||||||
|
WebsocketNotification.PLAYBACK_SOURCE: self._async_update_sources,
|
||||||
WebsocketNotification.PLAYBACK_STATE: self._async_update_playback_state,
|
WebsocketNotification.PLAYBACK_STATE: self._async_update_playback_state,
|
||||||
WebsocketNotification.REMOTE_MENU_CHANGED: self._async_update_sources,
|
WebsocketNotification.REMOTE_MENU_CHANGED: self._async_update_sources,
|
||||||
WebsocketNotification.SOURCE_CHANGE: self._async_update_source_change,
|
WebsocketNotification.SOURCE_CHANGE: self._async_update_source_change,
|
||||||
|
@ -243,7 +243,7 @@ class BangOlufsenMediaPlayer(BangOlufsenEntity, MediaPlayerEntity):
|
||||||
if queue_settings.shuffle is not None:
|
if queue_settings.shuffle is not None:
|
||||||
self._attr_shuffle = queue_settings.shuffle
|
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."""
|
"""Get sources for the specific product."""
|
||||||
|
|
||||||
# Audio sources
|
# Audio sources
|
||||||
|
@ -270,10 +270,7 @@ class BangOlufsenMediaPlayer(BangOlufsenEntity, MediaPlayerEntity):
|
||||||
self._audio_sources = {
|
self._audio_sources = {
|
||||||
source.id: source.name
|
source.id: source.name
|
||||||
for source in cast(list[Source], sources.items)
|
for source in cast(list[Source], sources.items)
|
||||||
if source.is_enabled
|
if source.is_enabled and source.id and source.name and source.is_playable
|
||||||
and source.id
|
|
||||||
and source.name
|
|
||||||
and source.id not in HIDDEN_SOURCE_IDS
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Some sources are not Beolink expandable, meaning that they can't be joined by
|
# 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._client.get_playback_progress_notifications(
|
||||||
self.on_playback_progress_notification
|
self.on_playback_progress_notification
|
||||||
)
|
)
|
||||||
|
self._client.get_playback_source_notifications(
|
||||||
|
self.on_playback_source_notification
|
||||||
|
)
|
||||||
self._client.get_playback_state_notifications(
|
self._client.get_playback_state_notifications(
|
||||||
self.on_playback_state_notification
|
self.on_playback_state_notification
|
||||||
)
|
)
|
||||||
|
@ -157,6 +160,14 @@ class BangOlufsenWebsocket(BangOlufsenBase):
|
||||||
notification,
|
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:
|
def on_source_change_notification(self, notification: Source) -> None:
|
||||||
"""Send source_change dispatch."""
|
"""Send source_change dispatch."""
|
||||||
async_dispatcher_send(
|
async_dispatcher_send(
|
||||||
|
|
|
@ -124,7 +124,7 @@ def mock_mozart_client() -> Generator[AsyncMock]:
|
||||||
client.get_available_sources = AsyncMock()
|
client.get_available_sources = AsyncMock()
|
||||||
client.get_available_sources.return_value = SourceArray(
|
client.get_available_sources.return_value = SourceArray(
|
||||||
items=[
|
items=[
|
||||||
# Is in the HIDDEN_SOURCE_IDS constant, so should not be user selectable
|
# Is not playable, so should not be user selectable
|
||||||
Source(
|
Source(
|
||||||
name="AirPlay",
|
name="AirPlay",
|
||||||
id="airPlay",
|
id="airPlay",
|
||||||
|
@ -137,14 +137,16 @@ def mock_mozart_client() -> Generator[AsyncMock]:
|
||||||
id="tidal",
|
id="tidal",
|
||||||
is_enabled=True,
|
is_enabled=True,
|
||||||
is_multiroom_available=True,
|
is_multiroom_available=True,
|
||||||
|
is_playable=True,
|
||||||
),
|
),
|
||||||
Source(
|
Source(
|
||||||
name="Line-In",
|
name="Line-In",
|
||||||
id="lineIn",
|
id="lineIn",
|
||||||
is_enabled=True,
|
is_enabled=True,
|
||||||
is_multiroom_available=False,
|
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(
|
Source(
|
||||||
name="Powerlink",
|
name="Powerlink",
|
||||||
id="pl",
|
id="pl",
|
||||||
|
|
|
@ -130,6 +130,7 @@ TEST_VIDEO_SOURCES = ["HDMI A"]
|
||||||
TEST_SOURCES = TEST_AUDIO_SOURCES + TEST_VIDEO_SOURCES
|
TEST_SOURCES = TEST_AUDIO_SOURCES + TEST_VIDEO_SOURCES
|
||||||
TEST_FALLBACK_SOURCES = [
|
TEST_FALLBACK_SOURCES = [
|
||||||
"Audio Streamer",
|
"Audio Streamer",
|
||||||
|
"Bluetooth",
|
||||||
"Spotify Connect",
|
"Spotify Connect",
|
||||||
"Line-In",
|
"Line-In",
|
||||||
"Optical",
|
"Optical",
|
||||||
|
|
|
@ -10,6 +10,7 @@ from mozart_api.models import (
|
||||||
PlayQueueSettings,
|
PlayQueueSettings,
|
||||||
RenderingState,
|
RenderingState,
|
||||||
Source,
|
Source,
|
||||||
|
SourceArray,
|
||||||
WebsocketNotificationTag,
|
WebsocketNotificationTag,
|
||||||
)
|
)
|
||||||
import pytest
|
import pytest
|
||||||
|
@ -195,6 +196,37 @@ async def test_async_update_sources_remote(
|
||||||
assert mock_mozart_client.get_remote_menu.call_count == 2
|
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(
|
async def test_async_update_playback_metadata(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
mock_mozart_client: AsyncMock,
|
mock_mozart_client: AsyncMock,
|
||||||
|
|
Loading…
Reference in New Issue