Simplify Bang & Olufsen source determination (#130072)

pull/130229/head
Markus Jacobsen 2024-11-09 17:13:07 +01:00 committed by GitHub
parent b61580a937
commit 31b505828b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 24 additions and 131 deletions

View File

@ -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] = {

View File

@ -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

View File

@ -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 = [

View File

@ -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',

View File

@ -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,