1 """Support for Cambridge Audio AV Receiver."""
3 from __future__
import annotations
5 from datetime
import datetime
8 from aiostreammagic
import (
9 RepeatMode
as CambridgeRepeatMode,
16 MediaPlayerDeviceClass,
18 MediaPlayerEntityFeature,
29 CAMBRIDGE_MEDIA_TYPE_AIRABLE,
30 CAMBRIDGE_MEDIA_TYPE_INTERNET_RADIO,
31 CAMBRIDGE_MEDIA_TYPE_PRESET,
34 from .entity
import CambridgeAudioEntity, command
37 MediaPlayerEntityFeature.SELECT_SOURCE
38 | MediaPlayerEntityFeature.TURN_OFF
39 | MediaPlayerEntityFeature.TURN_ON
40 | MediaPlayerEntityFeature.PLAY_MEDIA
44 MediaPlayerEntityFeature.VOLUME_MUTE
45 | MediaPlayerEntityFeature.VOLUME_SET
46 | MediaPlayerEntityFeature.VOLUME_STEP
49 TRANSPORT_FEATURES: dict[TransportControl, MediaPlayerEntityFeature] = {
50 TransportControl.PLAY: MediaPlayerEntityFeature.PLAY,
51 TransportControl.PAUSE: MediaPlayerEntityFeature.PAUSE,
52 TransportControl.TRACK_NEXT: MediaPlayerEntityFeature.NEXT_TRACK,
53 TransportControl.TRACK_PREVIOUS: MediaPlayerEntityFeature.PREVIOUS_TRACK,
54 TransportControl.TOGGLE_REPEAT: MediaPlayerEntityFeature.REPEAT_SET,
55 TransportControl.TOGGLE_SHUFFLE: MediaPlayerEntityFeature.SHUFFLE_SET,
56 TransportControl.SEEK: MediaPlayerEntityFeature.SEEK,
57 TransportControl.STOP: MediaPlayerEntityFeature.STOP,
66 async_add_entities: AddEntitiesCallback,
68 """Set up Cambridge Audio device based on a config entry."""
69 client: StreamMagicClient = entry.runtime_data
74 """Representation of a Cambridge Audio Media Player Device."""
77 _attr_media_content_type = MediaType.MUSIC
78 _attr_device_class = MediaPlayerDeviceClass.RECEIVER
80 def __init__(self, client: StreamMagicClient) ->
None:
81 """Initialize an Cambridge Audio entity."""
87 """Supported features for the media player."""
88 controls = self.
clientclient.now_playing.controls
89 features = BASE_FEATURES
90 if self.
clientclient.state.pre_amp_mode:
91 features |= PREAMP_FEATURES
92 if TransportControl.PLAY_PAUSE
in controls:
93 features |= MediaPlayerEntityFeature.PLAY | MediaPlayerEntityFeature.PAUSE
94 for control
in controls:
95 feature = TRANSPORT_FEATURES.get(control)
101 def state(self) -> MediaPlayerState:
102 """Return the state of the device."""
103 media_state = self.
clientclient.play_state.state
104 if media_state ==
"NETWORK":
105 return MediaPlayerState.STANDBY
106 if self.
clientclient.state.power:
107 if media_state ==
"play":
108 return MediaPlayerState.PLAYING
109 if media_state ==
"pause":
110 return MediaPlayerState.PAUSED
111 if media_state ==
"connecting":
112 return MediaPlayerState.BUFFERING
113 if media_state
in (
"stop",
"ready"):
114 return MediaPlayerState.IDLE
115 return MediaPlayerState.ON
116 return MediaPlayerState.OFF
120 """Return a list of available input sources."""
121 return [item.name
for item
in self.
clientclient.sources]
125 """Return the current input source."""
129 for item
in self.
clientclient.sources
130 if item.id == self.
clientclient.state.source
137 """Title of current playing media."""
138 return self.
clientclient.play_state.metadata.title
142 """Artist of current playing media, music track only."""
143 return self.
clientclient.play_state.metadata.artist
147 """Album name of current playing media, music track only."""
148 return self.
clientclient.play_state.metadata.album
152 """Image url of current playing media."""
153 return self.
clientclient.play_state.metadata.art_url
157 """Duration of the current media."""
158 return self.
clientclient.play_state.metadata.duration
162 """Position of the current media."""
163 return self.
clientclient.play_state.position
167 """Last time the media position was updated."""
168 return self.
clientclient.position_last_updated
172 """Volume mute status."""
173 return self.
clientclient.state.mute
177 """Current pre-amp volume level."""
178 volume = self.
clientclient.state.volume_percent
or 0
183 """Current shuffle configuration."""
184 return self.
clientclient.play_state.mode_shuffle != ShuffleMode.OFF
188 """Current repeat configuration."""
189 mode_repeat = RepeatMode.OFF
190 if self.
clientclient.play_state.mode_repeat == CambridgeRepeatMode.ALL:
191 mode_repeat = RepeatMode.ALL
196 """Toggle play/pause the current media."""
197 await self.
clientclient.play_pause()
201 """Pause the current media."""
202 controls = self.
clientclient.now_playing.controls
204 TransportControl.PAUSE
not in controls
205 and TransportControl.PLAY_PAUSE
in controls
207 await self.
clientclient.play_pause()
209 await self.
clientclient.pause()
213 """Stop the current media."""
214 await self.
clientclient.stop()
218 """Play the current media."""
219 controls = self.
clientclient.now_playing.controls
221 TransportControl.PLAY
not in controls
222 and TransportControl.PLAY_PAUSE
in controls
224 await self.
clientclient.play_pause()
226 await self.
clientclient.play()
230 """Skip to the next track."""
231 await self.
clientclient.next_track()
235 """Skip to the previous track."""
236 await self.
clientclient.previous_track()
240 """Select the source."""
241 for src
in self.
clientclient.sources:
242 if src.name == source:
243 await self.
clientclient.set_source_by_id(src.id)
248 """Power on the device."""
249 await self.
clientclient.power_on()
253 """Power off the device."""
254 await self.
clientclient.power_off()
258 """Step the volume up."""
259 await self.
clientclient.volume_up()
263 """Step the volume down."""
264 await self.
clientclient.volume_down()
268 """Set the volume level."""
269 await self.
clientclient.set_volume(
int(volume * 100))
273 """Set the mute state."""
274 await self.
clientclient.set_mute(mute)
278 """Seek to a position in the current media."""
283 """Set the shuffle mode for the current queue."""
284 shuffle_mode = ShuffleMode.OFF
286 shuffle_mode = ShuffleMode.ALL
291 """Set the repeat mode for the current queue."""
292 repeat_mode = CambridgeRepeatMode.OFF
293 if repeat
in {RepeatMode.ALL, RepeatMode.ONE}:
294 repeat_mode = CambridgeRepeatMode.ALL
299 self, media_type: MediaType | str, media_id: str, **kwargs: Any
301 """Play media on the Cambridge Audio device."""
303 if media_type
not in {
304 CAMBRIDGE_MEDIA_TYPE_PRESET,
305 CAMBRIDGE_MEDIA_TYPE_AIRABLE,
306 CAMBRIDGE_MEDIA_TYPE_INTERNET_RADIO,
309 translation_domain=DOMAIN,
310 translation_key=
"unsupported_media_type",
311 translation_placeholders={
"media_type": media_type},
314 if media_type == CAMBRIDGE_MEDIA_TYPE_PRESET:
316 preset_id =
int(media_id)
317 except ValueError
as ve:
319 translation_domain=DOMAIN,
320 translation_key=
"preset_non_integer",
321 translation_placeholders={
"preset_id": media_id},
324 for _preset
in self.
clientclient.preset_list.presets:
325 if _preset.preset_id == preset_id:
329 translation_domain=DOMAIN,
330 translation_key=
"missing_preset",
331 translation_placeholders={
"preset_id": media_id},
333 await self.
clientclient.recall_preset(preset.preset_id)
335 if media_type == CAMBRIDGE_MEDIA_TYPE_AIRABLE:
336 preset_id =
int(media_id)
337 await self.
clientclient.play_radio_airable(
"Radio", preset_id)
339 if media_type == CAMBRIDGE_MEDIA_TYPE_INTERNET_RADIO:
340 await self.
clientclient.play_radio_url(
"Radio", media_id)