diff --git a/homeassistant/components/music_assistant/media_player.py b/homeassistant/components/music_assistant/media_player.py index 0f8eb82f48a6..01a80d163d8a 100644 --- a/homeassistant/components/music_assistant/media_player.py +++ b/homeassistant/components/music_assistant/media_player.py @@ -20,7 +20,7 @@ from music_assistant_models.enums import ( ) from music_assistant_models.errors import MediaNotFoundError from music_assistant_models.event import MassEvent -from music_assistant_models.media_items import ItemMapping, MediaItemType, Track +from music_assistant_models.media_items import ItemMapping, MediaItemType from music_assistant_models.player_queue import PlayerQueue from homeassistant.components import media_source @@ -139,7 +139,6 @@ class MusicAssistantPlayer(MusicAssistantEntity, MediaPlayerEntity): self._attr_icon = self.player.icon.replace("mdi-", "mdi:") self._set_supported_features() self._attr_device_class = MediaPlayerDeviceClass.SPEAKER - self._prev_time: float = 0 self._source_list_mapping: dict[str, str] = {} self._sound_mode_list_mapping: dict[str, str] = {} @@ -148,23 +147,6 @@ class MusicAssistantPlayer(MusicAssistantEntity, MediaPlayerEntity): """Register callbacks.""" await super().async_added_to_hass() - # we subscribe to player queue time update but we only - # accept a state change on big time jumps (e.g. seeking) - async def queue_time_updated(event: MassEvent) -> None: - if event.object_id != self.player.active_source: - return - if abs((self._prev_time or 0) - event.data) > 5: - await self.async_on_update() - self.async_write_ha_state() - self._prev_time = event.data - - self.async_on_remove( - self.mass.subscribe( - queue_time_updated, - EventType.QUEUE_TIME_UPDATED, - ) - ) - # we subscribe to the player config changed event to update # the supported features of the player async def player_config_changed(event: MassEvent) -> None: @@ -669,88 +651,49 @@ class MusicAssistantPlayer(MusicAssistantEntity, MediaPlayerEntity): def _update_media_attributes( self, player: Player, queue: PlayerQueue | None ) -> None: - """Update media attributes for the active queue item.""" - self._attr_media_artist = None - self._attr_media_album_artist = None - self._attr_media_album_name = None - self._attr_media_title = None - self._attr_media_content_id = None - self._attr_media_duration = None - self._attr_media_position = None - self._attr_media_position_updated_at = None - - if queue is None and player.current_media: - # player has some external source active - self._attr_media_content_id = player.current_media.uri + """Update media attributes from the player's current media.""" + # shuffle and repeat are queue concepts and not part of current_media + if queue is not None: + self._attr_app_id = DOMAIN + self._attr_shuffle = queue.shuffle_enabled + self._attr_repeat = REPEAT_MODE_MAPPING_TO_HA.get(queue.repeat_mode) + else: self._attr_app_id = player.active_source - self._attr_media_title = player.current_media.title - self._attr_media_artist = player.current_media.artist - self._attr_media_album_name = player.current_media.album - self._attr_media_duration = player.current_media.duration - # shuffle and repeat are not (yet) supported for external sources self._attr_shuffle = None self._attr_repeat = None - self._attr_media_position = int(player.elapsed_time or 0) + + # the server resolves current_media for every playback scenario + current_media = player.current_media + self._attr_media_content_id = ( + current_media.uri if current_media is not None else None + ) + self._attr_media_title = ( + current_media.title if current_media is not None else None + ) + self._attr_media_artist = ( + current_media.artist if current_media is not None else None + ) + self._attr_media_album_name = ( + current_media.album if current_media is not None else None + ) + self._attr_media_album_artist = ( + current_media.album_artist if current_media is not None else None + ) + self._attr_media_duration = ( + current_media.duration if current_media is not None else None + ) + + # the server pushes a fresh position anchor on jumps (e.g. seeking) + if current_media is not None and current_media.elapsed_time is not None: + self._attr_media_position = int(current_media.elapsed_time) self._attr_media_position_updated_at = ( - utc_from_timestamp(player.elapsed_time_last_updated) - if player.elapsed_time_last_updated + utc_from_timestamp(current_media.elapsed_time_last_updated) + if current_media.elapsed_time_last_updated is not None else None ) - self._prev_time = player.elapsed_time or 0 - return - - if queue is None: - # player has no MA queue active - self._attr_source = player.active_source - self._attr_app_id = player.active_source - return - - # player has an MA queue active (either its own queue or some group queue) - self._attr_app_id = DOMAIN - self._attr_shuffle = queue.shuffle_enabled - self._attr_repeat = REPEAT_MODE_MAPPING_TO_HA.get(queue.repeat_mode) - if not (cur_item := queue.current_item): - # queue is empty - return - - self._attr_media_content_id = queue.current_item.uri - self._attr_media_duration = queue.current_item.duration - self._attr_media_position = int(queue.elapsed_time) - self._attr_media_position_updated_at = utc_from_timestamp( - queue.elapsed_time_last_updated - ) - self._prev_time = queue.elapsed_time - - # handle stream title (radio station icy metadata) - if (stream_details := cur_item.streamdetails) and stream_details.stream_title: - self._attr_media_album_name = cur_item.name - if " - " in stream_details.stream_title: - stream_title_parts = stream_details.stream_title.split(" - ", 1) - self._attr_media_title = stream_title_parts[1] - self._attr_media_artist = stream_title_parts[0] - else: - self._attr_media_title = stream_details.stream_title - return - - if not (media_item := cur_item.media_item): - # queue is not playing a regular media item (edge case?!) - self._attr_media_title = cur_item.name - return - - # queue is playing regular media item - self._attr_media_title = media_item.name - # for tracks we can extract more info - if media_item.media_type == MediaType.TRACK: - if TYPE_CHECKING: - assert isinstance(media_item, Track) - self._attr_media_artist = media_item.artist_str - if media_item.version: - self._attr_media_title += f" ({media_item.version})" - if media_item.album: - self._attr_media_album_name = media_item.album.name - self._attr_media_album_artist = getattr( - media_item.album, "artist_str", None - ) + else: + self._attr_media_position = None + self._attr_media_position_updated_at = None def _convert_queueoption_to_media_player_enqueue( self, queue_option: MediaPlayerEnqueue | QueueOption | None diff --git a/tests/components/music_assistant/fixtures/players.json b/tests/components/music_assistant/fixtures/players.json index 2ddf5a718553..70a8b18d354e 100644 --- a/tests/components/music_assistant/fixtures/players.json +++ b/tests/components/music_assistant/fixtures/players.json @@ -374,16 +374,19 @@ "active_source": "test_group_player_1", "active_group": null, "current_media": { - "uri": "http://192.168.1.1:8097/single/test_group_player_1/5d95dc5be77e4f7eb4939f62cfef527b.flac?ts=1730313038", - "media_type": "unknown", - "title": null, - "artist": null, - "album": null, + "uri": "spotify://track/3YRCqOhFifThpSRFJ1VWFM", + "media_type": "track", + "title": "November Rain", + "artist": "Guns N' Roses", + "album": "Use Your Illusion I", + "album_artist": "Guns N' Roses", "image_url": null, - "duration": null, + "duration": 536, "queue_id": "test_group_player_1", "queue_item_id": "5d95dc5be77e4f7eb4939f62cfef527b", - "custom_data": null + "custom_data": null, + "elapsed_time": 232, + "elapsed_time_last_updated": 1730313109.5659513 }, "synced_to": null, "enabled_by_default": true, diff --git a/tests/components/music_assistant/snapshots/test_media_player.ambr b/tests/components/music_assistant/snapshots/test_media_player.ambr index 95810a6612e0..b6c985dd8c38 100644 --- a/tests/components/music_assistant/snapshots/test_media_player.ambr +++ b/tests/components/music_assistant/snapshots/test_media_player.ambr @@ -55,7 +55,6 @@ : 'spotify://track/5d95dc5be77e4f7eb4939f62cfef527b', : , : 300, - : 0, : 'Test Track', : 'Spotify Connect', : , @@ -122,6 +121,7 @@ : 'mdi:speaker-multiple', : False, 'mass_player_type': 'group', + : "Guns N' Roses", : 'Use Your Illusion I', : "Guns N' Roses", : 'spotify://track/3YRCqOhFifThpSRFJ1VWFM',