6666
6767if TYPE_CHECKING :
6868 from music_assistant_models .config_entries import ConfigEntry , ConfigValueType , PlayerConfig
69+ from music_assistant_models .media_items import MediaItemPalette
6970 from music_assistant_models .player_queue import PlayerQueue
7071
7172 from .player_provider import PlayerProvider
@@ -98,6 +99,11 @@ class Player(ABC):
9899 _attr_active_source : str | None = None
99100 _attr_active_sound_mode : str | None = None
100101 _attr_current_media : PlayerMedia | None = None
102+ # Palette for the image currently shown, resolved asynchronously from the
103+ # cache controller and carried here so the (synchronous) state serialization
104+ # can read it back without blocking. See set_resolved_palette.
105+ _attr_current_palette : MediaItemPalette | None = None
106+ _attr_current_palette_url : str | None = None
101107 _attr_needs_poll : bool = False
102108 _attr_poll_interval : int = 30
103109 _attr_hidden_by_default : bool = False
@@ -1387,6 +1393,22 @@ def set_current_media( # noqa: PLR0913
13871393 if custom_data :
13881394 self ._attr_current_media .custom_data = custom_data
13891395
1396+ @final
1397+ def set_resolved_palette (self , image_url : str , palette : MediaItemPalette ) -> None :
1398+ """
1399+ Store the resolved color palette for the currently shown image.
1400+
1401+ The palette is resolved asynchronously (from the cache controller) by the
1402+ PlayerController; it is carried on the player here so the synchronous state
1403+ serialization can attach it without blocking. May only be called by the
1404+ PlayerController.
1405+
1406+ :param image_url: Image URL the palette was extracted from.
1407+ :param palette: The extracted color palette.
1408+ """
1409+ self ._attr_current_palette_url = image_url
1410+ self ._attr_current_palette = palette
1411+
13901412 @final
13911413 def set_config (self , config : PlayerConfig ) -> None :
13921414 """
@@ -1764,9 +1786,6 @@ def __final_active_group(self) -> str | None:
17641786 @final
17651787 def __final_current_media (self ) -> PlayerMedia | None :
17661788 """Return the FINAL current media for the player."""
1767- # Lazy import to avoid helpers.colors -> helpers.images -> models.plugin cycle.
1768- from music_assistant .helpers .colors import peek_palette_for_url # noqa: PLC0415
1769-
17701789 # if the player is grouped/synced, use the current_media of the group/parent player
17711790 if parent_player_id := (self .__final_active_group or self .__final_synced_to ):
17721791 if parent_player_id != self .player_id and (
@@ -1804,7 +1823,7 @@ def __final_current_media(self) -> PlayerMedia | None:
18041823 artist = stream_metadata .artist ,
18051824 album = stream_metadata .album or stream_metadata .description or current_item .name ,
18061825 image_url = image_url ,
1807- palette = peek_palette_for_url (image_url ),
1826+ palette = self . _resolved_palette (image_url ),
18081827 duration = stream_metadata .duration or current_item .duration ,
18091828 source_id = active_queue .queue_id ,
18101829 queue_item_id = current_item .queue_item_id ,
@@ -1836,7 +1855,7 @@ def __final_current_media(self) -> PlayerMedia | None:
18361855 artist = getattr (media_item , "artist_str" , None ),
18371856 album = album .name if album else podcast .name if podcast else description ,
18381857 image_url = image_url ,
1839- palette = peek_palette_for_url (image_url ),
1858+ palette = self . _resolved_palette (image_url ),
18401859 duration = media_item .duration ,
18411860 source_id = active_queue .queue_id ,
18421861 queue_item_id = current_item .queue_item_id ,
@@ -1850,7 +1869,7 @@ def __final_current_media(self) -> PlayerMedia | None:
18501869 media_type = current_item .media_type ,
18511870 title = current_item .name ,
18521871 image_url = item_image_url ,
1853- palette = peek_palette_for_url (item_image_url ),
1872+ palette = self . _resolved_palette (item_image_url ),
18541873 duration = current_item .duration ,
18551874 source_id = active_queue .queue_id ,
18561875 queue_item_id = current_item .queue_item_id ,
@@ -1870,7 +1889,7 @@ def __final_current_media(self) -> PlayerMedia | None:
18701889 artist = self .current_media .artist ,
18711890 album = self .current_media .album ,
18721891 image_url = image_url ,
1873- palette = peek_palette_for_url (image_url ),
1892+ palette = self . _resolved_palette (image_url ),
18741893 duration = self .current_media .duration ,
18751894 source_id = self .current_media .source_id or active_source ,
18761895 queue_item_id = self .current_media .queue_item_id ,
@@ -1882,6 +1901,12 @@ def __final_current_media(self) -> PlayerMedia | None:
18821901 )
18831902 return None
18841903
1904+ def _resolved_palette (self , image_url : str | None ) -> MediaItemPalette | None :
1905+ """Return the carried palette if it matches image_url, else None."""
1906+ if image_url and image_url == self ._attr_current_palette_url :
1907+ return self ._attr_current_palette
1908+ return None
1909+
18851910 @cached_property
18861911 @final
18871912 def __final_source_list (self ) -> UniqueList [PlayerSource ]:
0 commit comments