1 """Provide functionality to interact with the vlc telnet interface."""
3 from __future__
import annotations
5 from collections.abc
import Awaitable, Callable, Coroutine
6 from functools
import wraps
7 from typing
import Any, Concatenate, Literal
9 from aiovlc.client
import Client
10 from aiovlc.exceptions
import AuthError, CommandError, ConnectError
16 MediaPlayerEntityFeature,
19 async_process_play_media_url,
28 from .
import VlcConfigEntry
29 from .const
import DEFAULT_NAME, DOMAIN, LOGGER
34 def _get_str(data: dict, key: str) -> str |
None:
35 """Get a value from a dictionary and cast it to a string or None."""
36 if value := data.get(key):
42 hass: HomeAssistant, entry: VlcConfigEntry, async_add_entities: AddEntitiesCallback
44 """Set up the vlc platform."""
46 name = entry.data.get(CONF_NAME)
or DEFAULT_NAME
47 vlc = entry.runtime_data.vlc
48 available = entry.runtime_data.available
53 def catch_vlc_errors[_VlcDeviceT: VlcDevice, **_P](
54 func: Callable[Concatenate[_VlcDeviceT, _P], Awaitable[
None]],
55 ) -> Callable[Concatenate[_VlcDeviceT, _P], Coroutine[Any, Any,
None]]:
56 """Catch VLC errors."""
59 async
def wrapper(self: _VlcDeviceT, *args: _P.args, **kwargs: _P.kwargs) ->
None:
60 """Catch VLC errors and modify availability."""
62 await func(self, *args, **kwargs)
63 except CommandError
as err:
64 LOGGER.error(
"Command error: %s", err)
65 except ConnectError
as err:
66 if self._attr_available:
67 LOGGER.error(
"Connection error: %s", err)
68 self._attr_available =
False
74 """Representation of a vlc player."""
76 _attr_has_entity_name =
True
78 _attr_media_content_type = MediaType.MUSIC
79 _attr_supported_features = (
80 MediaPlayerEntityFeature.CLEAR_PLAYLIST
81 | MediaPlayerEntityFeature.NEXT_TRACK
82 | MediaPlayerEntityFeature.PAUSE
83 | MediaPlayerEntityFeature.PLAY
84 | MediaPlayerEntityFeature.PLAY_MEDIA
85 | MediaPlayerEntityFeature.PREVIOUS_TRACK
86 | MediaPlayerEntityFeature.SEEK
87 | MediaPlayerEntityFeature.SHUFFLE_SET
88 | MediaPlayerEntityFeature.STOP
89 | MediaPlayerEntityFeature.VOLUME_MUTE
90 | MediaPlayerEntityFeature.VOLUME_SET
91 | MediaPlayerEntityFeature.BROWSE_MEDIA
97 self, config_entry: ConfigEntry, vlc: Client, name: str, available: bool
99 """Initialize the vlc device."""
103 config_entry_id = config_entry.entry_id
106 entry_type=DeviceEntryType.SERVICE,
107 identifiers={(DOMAIN, config_entry_id)},
108 manufacturer=
"VideoLAN",
115 """Get the latest details from the device."""
118 await self.
_vlc_vlc.connect()
119 except ConnectError
as err:
120 LOGGER.debug(
"Connection error: %s", err)
124 await self.
_vlc_vlc.login()
126 LOGGER.debug(
"Failed to login to VLC")
127 self.
hasshass.async_create_task(
128 self.
hasshass.config_entries.async_reload(self.
_config_entry_config_entry.entry_id)
134 LOGGER.debug(
"Connected to vlc host: %s", self.
_vlc_vlc.host)
136 status = await self.
_vlc_vlc.status()
137 LOGGER.debug(
"Status: %s", status)
141 if state ==
"playing":
142 self.
_attr_state_attr_state = MediaPlayerState.PLAYING
143 elif state ==
"paused":
144 self.
_attr_state_attr_state = MediaPlayerState.PAUSED
146 self.
_attr_state_attr_state = MediaPlayerState.IDLE
148 if self.
_attr_state_attr_state != MediaPlayerState.IDLE:
150 time_output = await self.
_vlc_vlc.get_time()
151 vlc_position = time_output.time
158 info = await self.
_vlc_vlc.info()
160 LOGGER.debug(
"Info data: %s", data)
165 now_playing =
_get_str(data.get(
"data", {}),
"now_playing")
177 if data_info := data.get(
"data"):
178 media_title =
_get_str(data_info,
"filename")
181 if media_title
and (pos := media_title.find(
"?authSig=")) != -1:
188 """Seek the media to a specific location."""
189 await self.
_vlc_vlc.seek(round(position))
193 """Mute the volume."""
205 """Set volume level, range 0..1."""
206 await self.
_vlc_vlc.set_volume(round(volume * MAX_VOLUME))
215 """Send play command."""
216 status = await self.
_vlc_vlc.status()
217 if status.state ==
"paused":
219 await self.
_vlc_vlc.pause()
221 await self.
_vlc_vlc.play()
222 self.
_attr_state_attr_state = MediaPlayerState.PLAYING
226 """Send pause command."""
227 status = await self.
_vlc_vlc.status()
228 if status.state !=
"paused":
230 await self.
_vlc_vlc.pause()
232 self.
_attr_state_attr_state = MediaPlayerState.PAUSED
236 """Send stop command."""
237 await self.
_vlc_vlc.stop()
238 self.
_attr_state_attr_state = MediaPlayerState.IDLE
242 self, media_type: MediaType | str, media_id: str, **kwargs: Any
244 """Play media from a URL or file."""
246 if media_source.is_media_source_id(media_id):
247 sourced_media = await media_source.async_resolve_media(
250 media_id = sourced_media.url
254 self.
hasshass, media_id, for_supervisor_network=self.
_using_addon_using_addon
257 await self.
_vlc_vlc.
add(media_id)
258 self.
_attr_state_attr_state = MediaPlayerState.PLAYING
262 """Send previous track command."""
263 await self.
_vlc_vlc.prev()
267 """Send next track command."""
268 await self.
_vlc_vlc.next()
272 """Clear players playlist."""
273 await self.
_vlc_vlc.clear()
277 """Enable/disable shuffle mode."""
278 shuffle_command: Literal[
"on",
"off"] =
"on" if shuffle
else "off"
279 await self.
_vlc_vlc.random(shuffle_command)
283 media_content_type: MediaType | str |
None =
None,
284 media_content_id: str |
None =
None,
286 """Implement the websocket media browsing helper."""
287 return await media_source.async_browse_media(self.
hasshass, media_content_id)
bool add(self, _T matcher)