diff --git a/homeassistant/components/bang_olufsen/const.py b/homeassistant/components/bang_olufsen/const.py index 075be291103..5dce19cf8cb 100644 --- a/homeassistant/components/bang_olufsen/const.py +++ b/homeassistant/components/bang_olufsen/const.py @@ -22,6 +22,7 @@ class BeoSource: NET_RADIO: Final[Source] = Source(name="B&O Radio", id="netRadio") SPDIF: Final[Source] = Source(name="Optical", id="spdif") TIDAL: Final[Source] = Source(name="Tidal", id="tidal") + TV: Final[Source] = Source(name="TV", id="tv") UNKNOWN: Final[Source] = Source(name="Unknown Source", id="unknown") URI_STREAMER: Final[Source] = Source(name="Audio Streamer", id="uriStreamer") @@ -55,12 +56,13 @@ BEO_REPEAT_TO_HA: dict[str, RepeatMode] = { class BeoMediaType(StrEnum): """Bang & Olufsen specific media types.""" - FAVOURITE = "favourite" DEEZER = "deezer" + FAVOURITE = "favourite" + OVERLAY_TTS = "overlay_tts" RADIO = "radio" TIDAL = "tidal" TTS = "provider" - OVERLAY_TTS = "overlay_tts" + TV = "tv" class BeoModel(StrEnum): diff --git a/homeassistant/components/bang_olufsen/media_player.py b/homeassistant/components/bang_olufsen/media_player.py index bd1f1178053..e7a958502e8 100644 --- a/homeassistant/components/bang_olufsen/media_player.py +++ b/homeassistant/components/bang_olufsen/media_player.py @@ -218,6 +218,7 @@ class BeoMediaPlayer(BeoEntity, MediaPlayerEntity): self._sources: dict[str, str] = {} self._state: str = MediaPlayerState.IDLE self._video_sources: dict[str, str] = {} + self._video_source_id_map: dict[str, str] = {} self._sound_modes: dict[str, int] = {} # Beolink compatible sources @@ -355,6 +356,9 @@ class BeoMediaPlayer(BeoEntity, MediaPlayerEntity): and menu_item.label != "TV" ): self._video_sources[key] = menu_item.label + self._video_source_id_map[ + menu_item.content.content_uri.removeprefix("tv://") + ] = menu_item.label # Combine the source dicts self._sources = self._audio_sources | self._video_sources @@ -627,10 +631,11 @@ class BeoMediaPlayer(BeoEntity, MediaPlayerEntity): def media_content_type(self) -> MediaType | str | None: """Return the current media type.""" content_type = { - BeoSource.URI_STREAMER.id: MediaType.URL, BeoSource.DEEZER.id: BeoMediaType.DEEZER, - BeoSource.TIDAL.id: BeoMediaType.TIDAL, BeoSource.NET_RADIO.id: BeoMediaType.RADIO, + BeoSource.TIDAL.id: BeoMediaType.TIDAL, + BeoSource.TV.id: BeoMediaType.TV, + BeoSource.URI_STREAMER.id: MediaType.URL, } # Hard to determine content type. if self._source_change.id in content_type: @@ -690,7 +695,11 @@ class BeoMediaPlayer(BeoEntity, MediaPlayerEntity): @property def source(self) -> str | None: - """Return the current audio source.""" + """Return the current audio/video source.""" + # Associate TV content ID with a video source + if self.media_content_id in self._video_source_id_map: + return self._video_source_id_map[self.media_content_id] + return self._source_change.name @property diff --git a/tests/components/bang_olufsen/const.py b/tests/components/bang_olufsen/const.py index e886d18ad72..e0726778b4a 100644 --- a/tests/components/bang_olufsen/const.py +++ b/tests/components/bang_olufsen/const.py @@ -180,6 +180,14 @@ TEST_PLAYBACK_METADATA = PlaybackContentMetadata( track=1, source_internal_id="123", ) +TEST_PLAYBACK_METADATA_VIDEO = PlaybackContentMetadata( + encoding="unknown", + organization="HDMI A", + title="HDMI A", + source_internal_id="hdmi_1", + output_channel_processing="TrueImage", + output_Channels="5.0.2", +) TEST_PLAYBACK_ERROR = PlaybackError(error="Test error") TEST_PLAYBACK_PROGRESS = PlaybackProgress(progress=123) TEST_PLAYBACK_STATE_PAUSED = RenderingState(value="paused") diff --git a/tests/components/bang_olufsen/test_media_player.py b/tests/components/bang_olufsen/test_media_player.py index 38593922e30..729be234735 100644 --- a/tests/components/bang_olufsen/test_media_player.py +++ b/tests/components/bang_olufsen/test_media_player.py @@ -100,6 +100,7 @@ from .const import ( TEST_OVERLAY_OFFSET_VOLUME_TTS, TEST_PLAYBACK_ERROR, TEST_PLAYBACK_METADATA, + TEST_PLAYBACK_METADATA_VIDEO, TEST_PLAYBACK_PROGRESS, TEST_PLAYBACK_STATE_PAUSED, TEST_PLAYBACK_STATE_PLAYING, @@ -433,6 +434,36 @@ async def test_async_update_source_change( assert (ATTR_MEDIA_CONTENT_ID in states.attributes) == content_id_available +async def test_async_update_source_change_video( + hass: HomeAssistant, + integration: None, + mock_mozart_client: AsyncMock, +) -> None: + """Test _async_update_source_change with a video source.""" + playback_metadata_callback = ( + mock_mozart_client.get_playback_metadata_notifications.call_args[0][0] + ) + source_change_callback = ( + mock_mozart_client.get_source_change_notifications.call_args[0][0] + ) + + assert (states := hass.states.get(TEST_MEDIA_PLAYER_ENTITY_ID)) + assert ATTR_INPUT_SOURCE not in states.attributes + assert states.attributes[ATTR_MEDIA_CONTENT_TYPE] == MediaType.MUSIC + + # Simulate metadata and source change + playback_metadata_callback(TEST_PLAYBACK_METADATA_VIDEO) + source_change_callback(Source(id="tv", name="TV")) + + assert (states := hass.states.get(TEST_MEDIA_PLAYER_ENTITY_ID)) + assert states.attributes[ATTR_INPUT_SOURCE] == TEST_PLAYBACK_METADATA_VIDEO.title + assert states.attributes[ATTR_MEDIA_CONTENT_TYPE] == BeoMediaType.TV + assert ( + states.attributes[ATTR_MEDIA_CONTENT_ID] + == TEST_PLAYBACK_METADATA_VIDEO.source_internal_id + ) + + async def test_async_turn_off( hass: HomeAssistant, integration: None, @@ -819,7 +850,7 @@ async def test_async_select_source( audio_source_call: int, video_source_call: int, ) -> None: - """Test async_select_source with an invalid source.""" + """Test async_select_source with an invalid source and valid audio and video sources.""" with expected_result: await hass.services.async_call( MEDIA_PLAYER_DOMAIN,