1 """Class to hold all media player accessories."""
6 from pyhap.characteristic
import Characteristic
7 from pyhap.const
import CATEGORY_SWITCH
11 ATTR_INPUT_SOURCE_LIST,
12 ATTR_MEDIA_VOLUME_LEVEL,
13 ATTR_MEDIA_VOLUME_MUTED,
14 DOMAIN
as MEDIA_PLAYER_DOMAIN,
15 SERVICE_SELECT_SOURCE,
16 MediaPlayerEntityFeature,
20 ATTR_SUPPORTED_FEATURES,
23 SERVICE_MEDIA_PLAY_PAUSE,
39 from .accessories
import TYPES, HomeAccessory
48 CHAR_VOLUME_CONTROL_TYPE,
51 EVENT_HOMEKIT_TV_REMOTE_KEY_PRESSED,
58 SERV_TELEVISION_SPEAKER,
60 from .type_remotes
import REMOTE_KEYS, RemoteInputSelectAccessory
61 from .util
import cleanup_name_for_homekit, get_media_player_features
63 _LOGGER = logging.getLogger(__name__)
68 MODE_FRIENDLY_NAME = {
69 FEATURE_ON_OFF:
"Power",
70 FEATURE_PLAY_PAUSE:
"Play-Pause",
71 FEATURE_PLAY_STOP:
"Play-Stop",
72 FEATURE_TOGGLE_MUTE:
"Mute",
75 MEDIA_PLAYER_OFF_STATES = (
83 @TYPES.register("MediaPlayer")
85 """Generate a Media Player accessory."""
88 """Initialize a Switch accessory object."""
89 super().
__init__(*args, category=CATEGORY_SWITCH)
92 self.chars: dict[str, Characteristic |
None] = {
94 FEATURE_PLAY_PAUSE:
None,
95 FEATURE_PLAY_STOP:
None,
96 FEATURE_TOGGLE_MUTE:
None,
99 CONF_FEATURE_LIST, get_media_player_features(state)
102 if FEATURE_ON_OFF
in feature_list:
104 serv_on_off = self.add_preload_service(
105 SERV_SWITCH, CHAR_NAME, unique_id=FEATURE_ON_OFF
107 serv_on_off.configure_char(CHAR_NAME, value=name)
108 self.chars[FEATURE_ON_OFF] = serv_on_off.configure_char(
109 CHAR_ON, value=
False, setter_callback=self.
set_on_offset_on_off
112 if FEATURE_PLAY_PAUSE
in feature_list:
114 serv_play_pause = self.add_preload_service(
115 SERV_SWITCH, CHAR_NAME, unique_id=FEATURE_PLAY_PAUSE
117 serv_play_pause.configure_char(CHAR_NAME, value=name)
118 self.chars[FEATURE_PLAY_PAUSE] = serv_play_pause.configure_char(
119 CHAR_ON, value=
False, setter_callback=self.
set_play_pauseset_play_pause
122 if FEATURE_PLAY_STOP
in feature_list:
124 serv_play_stop = self.add_preload_service(
125 SERV_SWITCH, CHAR_NAME, unique_id=FEATURE_PLAY_STOP
127 serv_play_stop.configure_char(CHAR_NAME, value=name)
128 self.chars[FEATURE_PLAY_STOP] = serv_play_stop.configure_char(
129 CHAR_ON, value=
False, setter_callback=self.
set_play_stopset_play_stop
132 if FEATURE_TOGGLE_MUTE
in feature_list:
134 serv_toggle_mute = self.add_preload_service(
135 SERV_SWITCH, CHAR_NAME, unique_id=FEATURE_TOGGLE_MUTE
137 serv_toggle_mute.configure_char(CHAR_NAME, value=name)
138 self.chars[FEATURE_TOGGLE_MUTE] = serv_toggle_mute.configure_char(
139 CHAR_ON, value=
False, setter_callback=self.
set_toggle_muteset_toggle_mute
144 """Generate name for individual service."""
145 return cleanup_name_for_homekit(
146 f
"{self.display_name} {MODE_FRIENDLY_NAME[mode]}"
150 """Move switch state to value if call came from HomeKit."""
151 _LOGGER.debug(
'%s: Set switch state for "on_off" to %s', self.
entity_identity_id, value)
152 service = SERVICE_TURN_ON
if value
else SERVICE_TURN_OFF
153 params = {ATTR_ENTITY_ID: self.
entity_identity_id}
157 """Move switch state to value if call came from HomeKit."""
159 '%s: Set switch state for "play_pause" to %s', self.
entity_identity_id, value
161 service = SERVICE_MEDIA_PLAY
if value
else SERVICE_MEDIA_PAUSE
162 params = {ATTR_ENTITY_ID: self.
entity_identity_id}
166 """Move switch state to value if call came from HomeKit."""
168 '%s: Set switch state for "play_stop" to %s', self.
entity_identity_id, value
170 service = SERVICE_MEDIA_PLAY
if value
else SERVICE_MEDIA_STOP
171 params = {ATTR_ENTITY_ID: self.
entity_identity_id}
175 """Move switch state to value if call came from HomeKit."""
177 '%s: Set switch state for "toggle_mute" to %s', self.
entity_identity_id, value
179 params = {ATTR_ENTITY_ID: self.
entity_identity_id, ATTR_MEDIA_VOLUME_MUTED: value}
180 self.
async_call_serviceasync_call_service(MEDIA_PLAYER_DOMAIN, SERVICE_VOLUME_MUTE, params)
184 """Update switch state after state changed."""
185 current_state = new_state.state
187 if on_off_char := self.chars[FEATURE_ON_OFF]:
188 hk_state = current_state
not in MEDIA_PLAYER_OFF_STATES
190 '%s: Set current state for "on_off" to %s', self.
entity_identity_id, hk_state
192 on_off_char.set_value(hk_state)
194 if play_pause_char := self.chars[FEATURE_PLAY_PAUSE]:
195 hk_state = current_state == STATE_PLAYING
197 '%s: Set current state for "play_pause" to %s',
201 play_pause_char.set_value(hk_state)
203 if play_stop_char := self.chars[FEATURE_PLAY_STOP]:
204 hk_state = current_state == STATE_PLAYING
206 '%s: Set current state for "play_stop" to %s',
210 play_stop_char.set_value(hk_state)
212 if toggle_mute_char := self.chars[FEATURE_TOGGLE_MUTE]:
213 mute_state = bool(new_state.attributes.get(ATTR_MEDIA_VOLUME_MUTED))
215 '%s: Set current state for "toggle_mute" to %s',
219 toggle_mute_char.set_value(mute_state)
222 @TYPES.register("TelevisionMediaPlayer")
224 """Generate a Television Media Player accessory."""
226 def __init__(self, *args: Any, **kwargs: Any) ->
None:
227 """Initialize a Television Media Player accessory object."""
229 MediaPlayerEntityFeature.SELECT_SOURCE,
231 ATTR_INPUT_SOURCE_LIST,
237 features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
239 self.chars_speaker: list[str] = []
242 MediaPlayerEntityFeature.PLAY | MediaPlayerEntityFeature.PAUSE
245 features & MediaPlayerEntityFeature.VOLUME_MUTE
246 or features & MediaPlayerEntityFeature.VOLUME_STEP
248 self.chars_speaker.extend(
249 (CHAR_NAME, CHAR_ACTIVE, CHAR_VOLUME_CONTROL_TYPE, CHAR_VOLUME_SELECTOR)
251 if features & MediaPlayerEntityFeature.VOLUME_SET:
252 self.chars_speaker.append(CHAR_VOLUME)
254 if CHAR_VOLUME_SELECTOR
in self.chars_speaker:
255 serv_speaker = self.add_preload_service(
256 SERV_TELEVISION_SPEAKER, self.chars_speaker
258 self.
serv_tvserv_tv.add_linked_service(serv_speaker)
260 name = f
"{self.display_name} Volume"
261 serv_speaker.configure_char(CHAR_NAME, value=name)
262 serv_speaker.configure_char(CHAR_ACTIVE, value=1)
265 CHAR_MUTE, value=
False, setter_callback=self.
set_muteset_mute
268 volume_control_type = 1
if CHAR_VOLUME
in self.chars_speaker
else 2
269 serv_speaker.configure_char(
270 CHAR_VOLUME_CONTROL_TYPE, value=volume_control_type
274 CHAR_VOLUME_SELECTOR, setter_callback=self.
set_volume_stepset_volume_step
277 if CHAR_VOLUME
in self.chars_speaker:
279 CHAR_VOLUME, setter_callback=self.
set_volumeset_volume
285 """Move switch state to value if call came from HomeKit."""
286 _LOGGER.debug(
'%s: Set switch state for "on_off" to %s', self.
entity_identity_id, value)
287 service = SERVICE_TURN_ON
if value
else SERVICE_TURN_OFF
288 params = {ATTR_ENTITY_ID: self.
entity_identity_id}
292 """Move switch state to value if call came from HomeKit."""
294 '%s: Set switch state for "toggle_mute" to %s', self.
entity_identity_id, value
296 params = {ATTR_ENTITY_ID: self.
entity_identity_id, ATTR_MEDIA_VOLUME_MUTED: value}
297 self.
async_call_serviceasync_call_service(MEDIA_PLAYER_DOMAIN, SERVICE_VOLUME_MUTE, params)
300 """Send volume step value if call came from HomeKit."""
301 _LOGGER.debug(
"%s: Set volume to %s", self.
entity_identity_id, value)
302 params = {ATTR_ENTITY_ID: self.
entity_identity_id, ATTR_MEDIA_VOLUME_LEVEL: value}
303 self.
async_call_serviceasync_call_service(MEDIA_PLAYER_DOMAIN, SERVICE_VOLUME_SET, params)
306 """Send volume step value if call came from HomeKit."""
307 _LOGGER.debug(
"%s: Step volume by %s", self.
entity_identity_id, value)
308 service = SERVICE_VOLUME_DOWN
if value
else SERVICE_VOLUME_UP
309 params = {ATTR_ENTITY_ID: self.
entity_identity_id}
313 """Send input set value if call came from HomeKit."""
314 _LOGGER.debug(
"%s: Set current input to %s", self.
entity_identity_id, value)
316 params = {ATTR_ENTITY_ID: self.
entity_identity_id, ATTR_INPUT_SOURCE: source_name}
317 self.
async_call_serviceasync_call_service(MEDIA_PLAYER_DOMAIN, SERVICE_SELECT_SOURCE, params)
320 """Send remote key value if call came from HomeKit."""
321 _LOGGER.debug(
"%s: Set remote key to %s", self.
entity_identity_id, value)
322 if (key_name := REMOTE_KEYS.get(value))
is None:
323 _LOGGER.warning(
"%s: Unhandled key press for %s", self.
entity_identity_id, value)
328 state_obj = self.
hasshass.states.get(self.
entity_identity_id)
330 state = state_obj.state
331 if state
in (STATE_PLAYING, STATE_PAUSED):
333 SERVICE_MEDIA_PLAY
if state == STATE_PAUSED
else SERVICE_MEDIA_PAUSE
336 service = SERVICE_MEDIA_PLAY_PAUSE
337 params = {ATTR_ENTITY_ID: self.
entity_identity_id}
342 self.
hasshass.bus.async_fire(
343 EVENT_HOMEKIT_TV_REMOTE_KEY_PRESSED,
344 {ATTR_KEY_NAME: key_name, ATTR_ENTITY_ID: self.
entity_identity_id},
349 """Update Television state after state changed."""
350 current_state = new_state.state
354 if current_state
not in MEDIA_PLAYER_OFF_STATES:
356 _LOGGER.debug(
"%s: Set current active state to %s", self.
entity_identity_id, hk_state)
360 if CHAR_VOLUME_SELECTOR
in self.chars_speaker:
361 current_mute_state = bool(new_state.attributes.get(ATTR_MEDIA_VOLUME_MUTED))
363 "%s: Set current mute state to %s",
367 self.
char_mutechar_mute.set_value(current_mute_state)
372 @TYPES.register("ReceiverMediaPlayer")
374 """Generate a Receiver Media Player accessory.
376 For HomeKit, a Receiver Media Player is exactly the same as a
377 Television Media Player except it has a different category
378 which will tell HomeKit how to render the device.
382 """Initialize a Receiver Media Player accessory object."""
383 super().
__init__(*args, category=CATEGORY_RECEIVER)
None async_call_service(self, str domain, str service, dict[str, Any]|None service_data, Any|None value=None)
None async_update_state(self, State new_state)
web.Response get(self, web.Request request, str config_key)