/
/
/
1"""FullyKiosk Player implementation."""
2
3from __future__ import annotations
4
5import asyncio
6import time
7from typing import TYPE_CHECKING
8
9from music_assistant_models.enums import IdentifierType, PlaybackState, PlayerFeature
10from music_assistant_models.errors import PlayerCommandFailed, PlayerUnavailableError
11
12from music_assistant.constants import CONF_ENTRY_OUTPUT_CODEC_DEFAULT_MP3
13from music_assistant.models.player import DeviceInfo, Player, PlayerMedia
14
15if TYPE_CHECKING:
16 from fullykiosk import FullyKiosk
17 from music_assistant_models.config_entries import ConfigEntry, ConfigValueType
18
19 from .provider import FullyKioskProvider
20
21AUDIOMANAGER_STREAM_MUSIC = 3
22
23
24class FullyKioskPlayer(Player):
25 """FullyKiosk Player implementation."""
26
27 def __init__(
28 self,
29 provider: FullyKioskProvider,
30 player_id: str,
31 fully_kiosk: FullyKiosk,
32 address: str,
33 ) -> None:
34 """Initialize the FullyKiosk Player."""
35 super().__init__(provider, player_id)
36 self.fully_kiosk = fully_kiosk
37 # Set player attributes
38 self._attr_supported_features = {PlayerFeature.PLAY_MEDIA, PlayerFeature.VOLUME_SET}
39 self._attr_name = self.fully_kiosk.deviceInfo["deviceName"]
40 self._attr_device_info = DeviceInfo(
41 model=self.fully_kiosk.deviceInfo["deviceModel"],
42 manufacturer=self.fully_kiosk.deviceInfo["deviceManufacturer"],
43 )
44 self._attr_device_info.add_identifier(IdentifierType.IP_ADDRESS, address)
45 self._attr_available = True
46 self._attr_needs_poll = True
47 self._attr_poll_interval = 10
48
49 @property
50 def requires_flow_mode(self) -> bool:
51 """Return if the player requires flow mode."""
52 return True
53
54 async def get_config_entries(
55 self,
56 action: str | None = None,
57 values: dict[str, ConfigValueType] | None = None,
58 ) -> list[ConfigEntry]:
59 """Return all (provider/player specific) Config Entries for the given player (if any)."""
60 return [
61 CONF_ENTRY_OUTPUT_CODEC_DEFAULT_MP3,
62 ]
63
64 def set_attributes(self) -> None:
65 """Set/update FullyKiosk player attributes."""
66 self._attr_name = self.fully_kiosk.deviceInfo["deviceName"]
67 for volume_dict in self.fully_kiosk.deviceInfo.get("audioVolumes", []):
68 if str(AUDIOMANAGER_STREAM_MUSIC) in volume_dict:
69 volume = volume_dict[str(AUDIOMANAGER_STREAM_MUSIC)]
70 self._attr_volume_level = volume
71 break
72 current_url = self.fully_kiosk.deviceInfo.get("soundUrlPlaying")
73 if not current_url:
74 self._attr_playback_state = PlaybackState.IDLE
75 self._attr_available = True
76
77 async def volume_set(self, volume_level: int) -> None:
78 """Send VOLUME_SET command to given player."""
79 await self.fully_kiosk.setAudioVolume(volume_level, AUDIOMANAGER_STREAM_MUSIC)
80 self._attr_volume_level = volume_level
81 self.update_state()
82
83 async def stop(self) -> None:
84 """Send STOP command to given player."""
85 await self.fully_kiosk.stopSound()
86 self._attr_playback_state = PlaybackState.IDLE
87 self._attr_current_media = None
88 self.update_state()
89
90 async def play(self) -> None:
91 """Handle PLAY command on the player."""
92 raise PlayerCommandFailed("Playback can not be resumed.")
93
94 async def play_media(self, media: PlayerMedia) -> None:
95 """Handle PLAY MEDIA on given player."""
96 url = await self.provider.mass.streams.resolve_stream_url(self.player_id, media)
97 await self.fully_kiosk.playSound(url, AUDIOMANAGER_STREAM_MUSIC)
98 self._attr_current_media = media
99 self._attr_elapsed_time = 0
100 self._attr_elapsed_time_last_updated = time.time()
101 self._attr_playback_state = PlaybackState.PLAYING
102 self.update_state()
103
104 async def poll(self) -> None:
105 """Poll player for state updates."""
106 try:
107 async with asyncio.timeout(15):
108 await self.fully_kiosk.getDeviceInfo()
109 self.set_attributes()
110 self.update_state()
111 except Exception as err:
112 msg = f"Unable to start the FullyKiosk connection ({err!s}"
113 raise PlayerUnavailableError(msg) from err
114