Read media metadata from player current media in Music Assistant (#175465)

Co-authored-by: Artur Pragacz <artur@pragacz.com>
This commit is contained in:
Marvin Schenkel
2026-08-21 17:06:08 +02:00
committed by GitHub
co-authored by Artur Pragacz
parent 5a754a1e08
commit 0be0fc24ed
3 changed files with 49 additions and 103 deletions
@@ -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
@@ -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,
@@ -55,7 +55,6 @@
<MediaPlayerEntityStateAttribute.MEDIA_CONTENT_ID: 'media_content_id'>: 'spotify://track/5d95dc5be77e4f7eb4939f62cfef527b',
<MediaPlayerEntityStateAttribute.MEDIA_CONTENT_TYPE: 'media_content_type'>: <MediaType.MUSIC: 'music'>,
<MediaPlayerEntityStateAttribute.MEDIA_DURATION: 'media_duration'>: 300,
<MediaPlayerEntityStateAttribute.MEDIA_POSITION: 'media_position'>: 0,
<MediaPlayerEntityStateAttribute.MEDIA_TITLE: 'media_title'>: 'Test Track',
<MediaPlayerEntityStateAttribute.INPUT_SOURCE: 'source'>: 'Spotify Connect',
<EntityStateAttribute.SUPPORTED_FEATURES: 'supported_features'>: <MediaPlayerEntityFeature: 8320959>,
@@ -122,6 +121,7 @@
<EntityStateAttribute.ICON: 'icon'>: 'mdi:speaker-multiple',
<MediaPlayerEntityStateAttribute.MEDIA_VOLUME_MUTED: 'is_volume_muted'>: False,
'mass_player_type': 'group',
<MediaPlayerEntityStateAttribute.MEDIA_ALBUM_ARTIST: 'media_album_artist'>: "Guns N' Roses",
<MediaPlayerEntityStateAttribute.MEDIA_ALBUM_NAME: 'media_album_name'>: 'Use Your Illusion I',
<MediaPlayerEntityStateAttribute.MEDIA_ARTIST: 'media_artist'>: "Guns N' Roses",
<MediaPlayerEntityStateAttribute.MEDIA_CONTENT_ID: 'media_content_id'>: 'spotify://track/3YRCqOhFifThpSRFJ1VWFM',