1 """Support for media metadata handling."""
3 from __future__
import annotations
8 from soco.core
import (
12 MUSIC_SRC_SPOTIFY_CONNECT,
16 from soco.data_structures
import DidlAudioBroadcast, DidlPlaylistContainer
17 from soco.music_library
import MusicLibrary
27 SONOS_STATE_TRANSITIONING,
30 SOURCE_SPOTIFY_CONNECT,
33 from .helpers
import soco_error
35 LINEIN_SOURCES = (MUSIC_SRC_TV, MUSIC_SRC_LINE_IN)
37 MUSIC_SRC_AIRPLAY: SOURCE_AIRPLAY,
38 MUSIC_SRC_TV: SOURCE_TV,
39 MUSIC_SRC_LINE_IN: SOURCE_LINEIN,
40 MUSIC_SRC_SPOTIFY_CONNECT: SOURCE_SPOTIFY_CONNECT,
42 UNAVAILABLE_VALUES = {
"",
"NOT_IMPLEMENTED",
None}
43 DURATION_SECONDS =
"duration_in_s"
44 POSITION_SECONDS =
"position_in_s"
48 """Parse a time-span into number of seconds."""
49 if timespan
in UNAVAILABLE_VALUES:
55 """Representation of the current Sonos media."""
57 def __init__(self, hass: HomeAssistant, soco: SoCo) ->
None:
58 """Initialize a SonosMedia."""
61 self.
play_modeplay_mode: str |
None =
None
66 self.
artistartist: str |
None =
None
67 self.
channelchannel: str |
None =
None
68 self.
durationduration: float |
None =
None
69 self.
image_urlimage_url: str |
None =
None
74 self.
titletitle: str |
None =
None
75 self.
uriuri: str |
None =
None
77 self.
positionposition: int |
None =
None
81 """Clear basic media info."""
95 """Clear the position attributes."""
101 """Return the soco MusicLibrary instance."""
102 return self.
socosoco.music_library
106 """Poll the speaker for current track info, add converted position values."""
107 track_info: dict[str, Any] = self.
socosoco.get_current_track_info()
108 track_info[DURATION_SECONDS] =
_timespan_secs(track_info.get(
"duration"))
109 track_info[POSITION_SECONDS] =
_timespan_secs(track_info.get(
"position"))
113 """Send a signal to media player(s) to write new states."""
117 """Query the speaker to update media metadata and position info."""
121 if not track_info[
"uri"]:
123 self.
uriuri = track_info[
"uri"]
125 audio_source = self.
socosoco.music_source_from_uri(self.
uriuri)
126 if source := SOURCE_MAPPING.get(audio_source):
128 if audio_source
in LINEIN_SOURCES:
130 self.
titletitle = source
133 self.
artistartist = track_info.get(
"artist")
134 self.
album_namealbum_name = track_info.get(
"album")
135 self.
titletitle = track_info.get(
"title")
136 self.
image_urlimage_url = track_info.get(
"album_art")
138 playlist_position =
int(track_info.get(
"playlist_position", -1))
139 if playlist_position > 0:
145 """Update information about currently playing media using an event payload."""
146 new_status = evars[
"transport_state"]
152 track_uri = evars[
"enqueued_transport_uri"]
or evars[
"current_track_uri"]
153 audio_source = self.
socosoco.music_source_from_uri(track_uri)
157 if ct_md := evars[
"current_track_meta_data"]:
159 if album_art_uri := getattr(ct_md,
"album_art_uri",
None):
164 et_uri_md = evars[
"enqueued_transport_uri_meta_data"]
165 if isinstance(et_uri_md, DidlPlaylistContainer):
168 if queue_size := evars.get(
"number_of_tracks", 0):
171 if audio_source == MUSIC_SRC_RADIO:
173 self.
channelchannel = et_uri_md.title
176 if ct_md
and hasattr(ct_md,
"radio_show")
and ct_md.radio_show:
177 radio_show = ct_md.radio_show.split(
",")[0]
178 self.
channelchannel =
" • ".join(filter(
None, [self.
channelchannel, radio_show]))
180 if isinstance(et_uri_md, DidlAudioBroadcast):
187 """Poll information about currently playing media."""
188 transport_info = self.
socosoco.get_current_transport_info()
189 new_status = transport_info[
"current_transport_state"]
191 if new_status == SONOS_STATE_TRANSITIONING:
203 self, position_info: dict[str, int], force_update: bool =
False
205 """Update state when playing music tracks."""
206 duration = position_info.get(DURATION_SECONDS)
207 current_position = position_info.get(POSITION_SECONDS)
209 if not (duration
or current_position):
213 should_update = force_update
217 if current_position
is not None and self.
positionposition
is None:
221 if current_position
is not None and self.
positionposition
is not None:
225 time_diff = time_delta.total_seconds()
229 calculated_position = self.
positionposition + time_diff
231 if abs(calculated_position - current_position) > 1.5:
234 if current_position
is None:
237 self.
positionposition = current_position
timedelta time_period_str(str value)
None dispatcher_send(HomeAssistant hass, str signal, *Any args)