Simplify Bang & Olufsen source determination (#130072)
parent
b61580a937
commit
31b505828b
|
@ -17,62 +17,9 @@ from homeassistant.components.media_player import (
|
||||||
class BangOlufsenSource:
|
class BangOlufsenSource:
|
||||||
"""Class used for associating device source ids with friendly names. May not include all sources."""
|
"""Class used for associating device source ids with friendly names. May not include all sources."""
|
||||||
|
|
||||||
URI_STREAMER: Final[Source] = Source(
|
LINE_IN: Final[Source] = Source(name="Line-In", id="lineIn")
|
||||||
name="Audio Streamer",
|
SPDIF: Final[Source] = Source(name="Optical", id="spdif")
|
||||||
id="uriStreamer",
|
URI_STREAMER: Final[Source] = Source(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,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
BANG_OLUFSEN_STATES: dict[str, MediaPlayerState] = {
|
BANG_OLUFSEN_STATES: dict[str, MediaPlayerState] = {
|
||||||
|
|
|
@ -688,36 +688,6 @@ class BangOlufsenMediaPlayer(BangOlufsenEntity, MediaPlayerEntity):
|
||||||
@property
|
@property
|
||||||
def source(self) -> str | None:
|
def source(self) -> str | None:
|
||||||
"""Return the current audio source."""
|
"""Return the current audio source."""
|
||||||
|
|
||||||
# Try to fix some of the source_change chromecast weirdness.
|
|
||||||
if hasattr(self._playback_metadata, "title"):
|
|
||||||
# source_change is chromecast but line in is selected.
|
|
||||||
if self._playback_metadata.title == BangOlufsenSource.LINE_IN.name:
|
|
||||||
return BangOlufsenSource.LINE_IN.name
|
|
||||||
|
|
||||||
# source_change is chromecast but bluetooth is selected.
|
|
||||||
if self._playback_metadata.title == BangOlufsenSource.BLUETOOTH.name:
|
|
||||||
return BangOlufsenSource.BLUETOOTH.name
|
|
||||||
|
|
||||||
# source_change is line in, bluetooth or optical but stale metadata is sent through the WebSocket,
|
|
||||||
# And the source has not changed.
|
|
||||||
if self._source_change.id in (
|
|
||||||
BangOlufsenSource.BLUETOOTH.id,
|
|
||||||
BangOlufsenSource.LINE_IN.id,
|
|
||||||
BangOlufsenSource.SPDIF.id,
|
|
||||||
):
|
|
||||||
return BangOlufsenSource.CHROMECAST.name
|
|
||||||
|
|
||||||
# source_change is chromecast and there is metadata but no artwork. Bluetooth does support metadata but not artwork
|
|
||||||
# So i assume that it is bluetooth and not chromecast
|
|
||||||
if (
|
|
||||||
hasattr(self._playback_metadata, "art")
|
|
||||||
and self._playback_metadata.art is not None
|
|
||||||
and len(self._playback_metadata.art) == 0
|
|
||||||
and self._source_change.id == BangOlufsenSource.CHROMECAST.id
|
|
||||||
):
|
|
||||||
return BangOlufsenSource.BLUETOOTH.name
|
|
||||||
|
|
||||||
return self._source_change.name
|
return self._source_change.name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
@ -16,6 +16,7 @@ from mozart_api.models import (
|
||||||
PlayQueueItemType,
|
PlayQueueItemType,
|
||||||
RenderingState,
|
RenderingState,
|
||||||
SceneProperties,
|
SceneProperties,
|
||||||
|
Source,
|
||||||
UserFlow,
|
UserFlow,
|
||||||
VolumeLevel,
|
VolumeLevel,
|
||||||
VolumeMute,
|
VolumeMute,
|
||||||
|
@ -125,7 +126,10 @@ TEST_DATA_ZEROCONF_IPV6 = ZeroconfServiceInfo(
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
TEST_AUDIO_SOURCES = [BangOlufsenSource.TIDAL.name, BangOlufsenSource.LINE_IN.name]
|
TEST_SOURCE = Source(
|
||||||
|
name="Tidal", id="tidal", is_seekable=True, is_enabled=True, is_playable=True
|
||||||
|
)
|
||||||
|
TEST_AUDIO_SOURCES = [TEST_SOURCE.name, BangOlufsenSource.LINE_IN.name]
|
||||||
TEST_VIDEO_SOURCES = ["HDMI A"]
|
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 = [
|
||||||
|
|
|
@ -573,7 +573,7 @@
|
||||||
'Test Listening Mode (234)',
|
'Test Listening Mode (234)',
|
||||||
'Test Listening Mode 2 (345)',
|
'Test Listening Mode 2 (345)',
|
||||||
]),
|
]),
|
||||||
'source': 'Chromecast built-in',
|
'source': 'Line-In',
|
||||||
'source_list': list([
|
'source_list': list([
|
||||||
'Tidal',
|
'Tidal',
|
||||||
'Line-In',
|
'Line-In',
|
||||||
|
|
|
@ -105,6 +105,7 @@ from .const import (
|
||||||
TEST_SEEK_POSITION_HOME_ASSISTANT_FORMAT,
|
TEST_SEEK_POSITION_HOME_ASSISTANT_FORMAT,
|
||||||
TEST_SOUND_MODE_2,
|
TEST_SOUND_MODE_2,
|
||||||
TEST_SOUND_MODES,
|
TEST_SOUND_MODES,
|
||||||
|
TEST_SOURCE,
|
||||||
TEST_SOURCES,
|
TEST_SOURCES,
|
||||||
TEST_VIDEO_SOURCES,
|
TEST_VIDEO_SOURCES,
|
||||||
TEST_VOLUME,
|
TEST_VOLUME,
|
||||||
|
@ -231,7 +232,7 @@ async def test_async_update_sources_availability(
|
||||||
|
|
||||||
# Add a source that is available and playable
|
# Add a source that is available and playable
|
||||||
mock_mozart_client.get_available_sources.return_value = SourceArray(
|
mock_mozart_client.get_available_sources.return_value = SourceArray(
|
||||||
items=[BangOlufsenSource.TIDAL]
|
items=[TEST_SOURCE]
|
||||||
)
|
)
|
||||||
|
|
||||||
# Send playback_source. The source is not actually used, so its attributes don't matter
|
# Send playback_source. The source is not actually used, so its attributes don't matter
|
||||||
|
@ -239,7 +240,7 @@ async def test_async_update_sources_availability(
|
||||||
|
|
||||||
assert mock_mozart_client.get_available_sources.call_count == 2
|
assert mock_mozart_client.get_available_sources.call_count == 2
|
||||||
assert (states := hass.states.get(TEST_MEDIA_PLAYER_ENTITY_ID))
|
assert (states := hass.states.get(TEST_MEDIA_PLAYER_ENTITY_ID))
|
||||||
assert states.attributes[ATTR_INPUT_SOURCE_LIST] == [BangOlufsenSource.TIDAL.name]
|
assert states.attributes[ATTR_INPUT_SOURCE_LIST] == [TEST_SOURCE.name]
|
||||||
|
|
||||||
|
|
||||||
async def test_async_update_playback_metadata(
|
async def test_async_update_playback_metadata(
|
||||||
|
@ -357,19 +358,17 @@ async def test_async_update_playback_state(
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("reported_source", "real_source", "content_type", "progress", "metadata"),
|
("source", "content_type", "progress", "metadata"),
|
||||||
[
|
[
|
||||||
# Normal source, music mediatype expected, no progress expected
|
# Normal source, music mediatype expected
|
||||||
(
|
(
|
||||||
BangOlufsenSource.TIDAL,
|
TEST_SOURCE,
|
||||||
BangOlufsenSource.TIDAL,
|
|
||||||
MediaType.MUSIC,
|
MediaType.MUSIC,
|
||||||
TEST_PLAYBACK_PROGRESS.progress,
|
TEST_PLAYBACK_PROGRESS.progress,
|
||||||
PlaybackContentMetadata(),
|
PlaybackContentMetadata(),
|
||||||
),
|
),
|
||||||
# URI source, url media type expected, no progress expected
|
# URI source, url media type expected
|
||||||
(
|
(
|
||||||
BangOlufsenSource.URI_STREAMER,
|
|
||||||
BangOlufsenSource.URI_STREAMER,
|
BangOlufsenSource.URI_STREAMER,
|
||||||
MediaType.URL,
|
MediaType.URL,
|
||||||
TEST_PLAYBACK_PROGRESS.progress,
|
TEST_PLAYBACK_PROGRESS.progress,
|
||||||
|
@ -378,44 +377,17 @@ async def test_async_update_playback_state(
|
||||||
# Line-In source,media type expected, progress 0 expected
|
# Line-In source,media type expected, progress 0 expected
|
||||||
(
|
(
|
||||||
BangOlufsenSource.LINE_IN,
|
BangOlufsenSource.LINE_IN,
|
||||||
BangOlufsenSource.CHROMECAST,
|
|
||||||
MediaType.MUSIC,
|
MediaType.MUSIC,
|
||||||
0,
|
0,
|
||||||
PlaybackContentMetadata(),
|
PlaybackContentMetadata(),
|
||||||
),
|
),
|
||||||
# Chromecast as source, but metadata says Line-In.
|
|
||||||
# Progress is not set to 0 as the source is Chromecast first
|
|
||||||
(
|
|
||||||
BangOlufsenSource.CHROMECAST,
|
|
||||||
BangOlufsenSource.LINE_IN,
|
|
||||||
MediaType.MUSIC,
|
|
||||||
TEST_PLAYBACK_PROGRESS.progress,
|
|
||||||
PlaybackContentMetadata(title=BangOlufsenSource.LINE_IN.name),
|
|
||||||
),
|
|
||||||
# Chromecast as source, but metadata says Bluetooth
|
|
||||||
(
|
|
||||||
BangOlufsenSource.CHROMECAST,
|
|
||||||
BangOlufsenSource.BLUETOOTH,
|
|
||||||
MediaType.MUSIC,
|
|
||||||
TEST_PLAYBACK_PROGRESS.progress,
|
|
||||||
PlaybackContentMetadata(title=BangOlufsenSource.BLUETOOTH.name),
|
|
||||||
),
|
|
||||||
# Chromecast as source, but metadata says Bluetooth in another way
|
|
||||||
(
|
|
||||||
BangOlufsenSource.CHROMECAST,
|
|
||||||
BangOlufsenSource.BLUETOOTH,
|
|
||||||
MediaType.MUSIC,
|
|
||||||
TEST_PLAYBACK_PROGRESS.progress,
|
|
||||||
PlaybackContentMetadata(art=[]),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
async def test_async_update_source_change(
|
async def test_async_update_source_change(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
mock_mozart_client: AsyncMock,
|
mock_mozart_client: AsyncMock,
|
||||||
mock_config_entry: MockConfigEntry,
|
mock_config_entry: MockConfigEntry,
|
||||||
reported_source: Source,
|
source: Source,
|
||||||
real_source: Source,
|
|
||||||
content_type: MediaType,
|
content_type: MediaType,
|
||||||
progress: int,
|
progress: int,
|
||||||
metadata: PlaybackContentMetadata,
|
metadata: PlaybackContentMetadata,
|
||||||
|
@ -444,10 +416,10 @@ async def test_async_update_source_change(
|
||||||
|
|
||||||
# Simulate metadata
|
# Simulate metadata
|
||||||
playback_metadata_callback(metadata)
|
playback_metadata_callback(metadata)
|
||||||
source_change_callback(reported_source)
|
source_change_callback(source)
|
||||||
|
|
||||||
assert (states := hass.states.get(TEST_MEDIA_PLAYER_ENTITY_ID))
|
assert (states := hass.states.get(TEST_MEDIA_PLAYER_ENTITY_ID))
|
||||||
assert states.attributes[ATTR_INPUT_SOURCE] == real_source.name
|
assert states.attributes[ATTR_INPUT_SOURCE] == source.name
|
||||||
assert states.attributes[ATTR_MEDIA_CONTENT_TYPE] == content_type
|
assert states.attributes[ATTR_MEDIA_CONTENT_TYPE] == content_type
|
||||||
assert states.attributes[ATTR_MEDIA_POSITION] == progress
|
assert states.attributes[ATTR_MEDIA_POSITION] == progress
|
||||||
|
|
||||||
|
@ -774,7 +746,7 @@ async def test_async_media_next_track(
|
||||||
("source", "expected_result", "seek_called_times"),
|
("source", "expected_result", "seek_called_times"),
|
||||||
[
|
[
|
||||||
# Seekable source, seek expected
|
# Seekable source, seek expected
|
||||||
(BangOlufsenSource.DEEZER, does_not_raise(), 1),
|
(TEST_SOURCE, does_not_raise(), 1),
|
||||||
# Non seekable source, seek shouldn't work
|
# Non seekable source, seek shouldn't work
|
||||||
(BangOlufsenSource.LINE_IN, pytest.raises(HomeAssistantError), 0),
|
(BangOlufsenSource.LINE_IN, pytest.raises(HomeAssistantError), 0),
|
||||||
# Malformed source, seek shouldn't work
|
# Malformed source, seek shouldn't work
|
||||||
|
@ -862,7 +834,7 @@ async def test_async_clear_playlist(
|
||||||
# Invalid source
|
# Invalid source
|
||||||
("Test source", pytest.raises(ServiceValidationError), 0, 0),
|
("Test source", pytest.raises(ServiceValidationError), 0, 0),
|
||||||
# Valid audio source
|
# Valid audio source
|
||||||
(BangOlufsenSource.TIDAL.name, does_not_raise(), 1, 0),
|
(TEST_SOURCE.name, does_not_raise(), 1, 0),
|
||||||
# Valid video source
|
# Valid video source
|
||||||
(TEST_VIDEO_SOURCES[0], does_not_raise(), 0, 1),
|
(TEST_VIDEO_SOURCES[0], does_not_raise(), 0, 1),
|
||||||
],
|
],
|
||||||
|
@ -1432,7 +1404,7 @@ async def test_async_join_players(
|
||||||
await hass.config_entries.async_setup(mock_config_entry_2.entry_id)
|
await hass.config_entries.async_setup(mock_config_entry_2.entry_id)
|
||||||
|
|
||||||
# Set the source to a beolink expandable source
|
# Set the source to a beolink expandable source
|
||||||
source_change_callback(BangOlufsenSource.TIDAL)
|
source_change_callback(TEST_SOURCE)
|
||||||
|
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
MEDIA_PLAYER_DOMAIN,
|
MEDIA_PLAYER_DOMAIN,
|
||||||
|
@ -1468,7 +1440,7 @@ async def test_async_join_players(
|
||||||
),
|
),
|
||||||
# Invalid media_player entity
|
# Invalid media_player entity
|
||||||
(
|
(
|
||||||
BangOlufsenSource.TIDAL,
|
TEST_SOURCE,
|
||||||
[TEST_MEDIA_PLAYER_ENTITY_ID_3],
|
[TEST_MEDIA_PLAYER_ENTITY_ID_3],
|
||||||
pytest.raises(ServiceValidationError),
|
pytest.raises(ServiceValidationError),
|
||||||
"invalid_grouping_entity",
|
"invalid_grouping_entity",
|
||||||
|
@ -1637,7 +1609,7 @@ async def test_async_beolink_expand(
|
||||||
)
|
)
|
||||||
|
|
||||||
# Set the source to a beolink expandable source
|
# Set the source to a beolink expandable source
|
||||||
source_change_callback(BangOlufsenSource.TIDAL)
|
source_change_callback(TEST_SOURCE)
|
||||||
|
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
|
|
Loading…
Reference in New Issue