python-linkplay 0.2.2__tar.gz → 0.2.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. {python_linkplay-0.2.2/src/python_linkplay.egg-info → python_linkplay-0.2.4}/PKG-INFO +1 -1
  2. python_linkplay-0.2.4/src/linkplay/__version__.py +1 -0
  3. {python_linkplay-0.2.2 → python_linkplay-0.2.4}/src/linkplay/bridge.py +49 -1
  4. {python_linkplay-0.2.2 → python_linkplay-0.2.4}/src/linkplay/consts.py +48 -0
  5. {python_linkplay-0.2.2 → python_linkplay-0.2.4}/src/linkplay/controller.py +13 -0
  6. {python_linkplay-0.2.2 → python_linkplay-0.2.4/src/python_linkplay.egg-info}/PKG-INFO +1 -1
  7. python_linkplay-0.2.2/src/linkplay/__version__.py +0 -1
  8. {python_linkplay-0.2.2 → python_linkplay-0.2.4}/LICENSE +0 -0
  9. {python_linkplay-0.2.2 → python_linkplay-0.2.4}/README.md +0 -0
  10. {python_linkplay-0.2.2 → python_linkplay-0.2.4}/pyproject.toml +0 -0
  11. {python_linkplay-0.2.2 → python_linkplay-0.2.4}/setup.cfg +0 -0
  12. {python_linkplay-0.2.2 → python_linkplay-0.2.4}/setup.py +0 -0
  13. {python_linkplay-0.2.2 → python_linkplay-0.2.4}/src/linkplay/__init__.py +0 -0
  14. {python_linkplay-0.2.2 → python_linkplay-0.2.4}/src/linkplay/__main__.py +0 -0
  15. {python_linkplay-0.2.2 → python_linkplay-0.2.4}/src/linkplay/discovery.py +0 -0
  16. {python_linkplay-0.2.2 → python_linkplay-0.2.4}/src/linkplay/endpoint.py +0 -0
  17. {python_linkplay-0.2.2 → python_linkplay-0.2.4}/src/linkplay/exceptions.py +0 -0
  18. {python_linkplay-0.2.2 → python_linkplay-0.2.4}/src/linkplay/manufacturers.py +0 -0
  19. {python_linkplay-0.2.2 → python_linkplay-0.2.4}/src/linkplay/utils.py +0 -0
  20. {python_linkplay-0.2.2 → python_linkplay-0.2.4}/src/python_linkplay.egg-info/SOURCES.txt +0 -0
  21. {python_linkplay-0.2.2 → python_linkplay-0.2.4}/src/python_linkplay.egg-info/dependency_links.txt +0 -0
  22. {python_linkplay-0.2.2 → python_linkplay-0.2.4}/src/python_linkplay.egg-info/not-zip-safe +0 -0
  23. {python_linkplay-0.2.2 → python_linkplay-0.2.4}/src/python_linkplay.egg-info/requires.txt +0 -0
  24. {python_linkplay-0.2.2 → python_linkplay-0.2.4}/src/python_linkplay.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python_linkplay
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: A Python Library for Seamless LinkPlay Device Control
5
5
  Author: Velleman Group nv
6
6
  License: MIT
@@ -0,0 +1 @@
1
+ __version__ = '0.2.4'
@@ -1,18 +1,21 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import time
4
- from typing import Any
4
+ from typing import Any, Callable
5
5
 
6
6
  from linkplay.consts import (
7
7
  INPUT_MODE_MAP,
8
8
  LOGGER,
9
9
  PLAY_MODE_SEND_MAP,
10
+ AudioOutputHwMode,
10
11
  ChannelType,
11
12
  DeviceAttribute,
12
13
  EqualizerMode,
13
14
  InputMode,
14
15
  LinkPlayCommand,
15
16
  LoopMode,
17
+ MetaInfo,
18
+ MetaInfoMetaData,
16
19
  MultiroomAttribute,
17
20
  MuteMode,
18
21
  PlayerAttribute,
@@ -36,6 +39,8 @@ class LinkPlayDevice:
36
39
  bridge: LinkPlayBridge
37
40
  properties: dict[DeviceAttribute, str]
38
41
 
42
+ controller: Callable[[], None] | None = None
43
+
39
44
  def __init__(self, bridge: LinkPlayBridge):
40
45
  self.bridge = bridge
41
46
  self.properties = dict.fromkeys(DeviceAttribute.__members__.values(), "")
@@ -44,6 +49,10 @@ class LinkPlayDevice:
44
49
  """Return the state of the LinkPlayDevice."""
45
50
  return {"properties": self.properties}
46
51
 
52
+ def set_callback(self, controller: Callable[[], None]) -> None:
53
+ """Sets a callback function to notify events."""
54
+ self.controller = controller
55
+
47
56
  async def update_status(self) -> None:
48
57
  """Update the device status."""
49
58
  self.properties = await self.bridge.json_request(LinkPlayCommand.DEVICE_STATUS) # type: ignore[assignment]
@@ -122,11 +131,15 @@ class LinkPlayPlayer:
122
131
  bridge: LinkPlayBridge
123
132
  properties: dict[PlayerAttribute, str]
124
133
  custom_properties: dict[PlayerAttribute, str]
134
+ metainfo: dict[MetaInfo, dict[MetaInfoMetaData, str]]
135
+
136
+ previous_playing_mode: PlayingMode | None = None
125
137
 
126
138
  def __init__(self, bridge: LinkPlayBridge):
127
139
  self.bridge = bridge
128
140
  self.properties = dict.fromkeys(PlayerAttribute.__members__.values(), "")
129
141
  self.custom_properties = dict.fromkeys(PlayerAttribute.__members__.values(), "")
142
+ self.metainfo = dict.fromkeys(MetaInfo.__members__.values(), {})
130
143
 
131
144
  def to_dict(self):
132
145
  """Return the state of the LinkPlayPlayer."""
@@ -139,6 +152,26 @@ class LinkPlayPlayer:
139
152
  ) # type: ignore[assignment]
140
153
 
141
154
  self.properties = fixup_player_properties(properties)
155
+ if self.bridge.device.manufacturer == MANUFACTURER_WIIM:
156
+ self.metainfo: dict[
157
+ MetaInfo, dict[MetaInfoMetaData, str]
158
+ ] = await self.bridge.json_request(LinkPlayCommand.META_INFO) # type: ignore[assignment]
159
+ else:
160
+ self.metainfo = {}
161
+
162
+ # handle multiroom changes
163
+ if self.bridge.device.controller is not None and (
164
+ (
165
+ self.previous_playing_mode != PlayingMode.FOLLOWER
166
+ and self.play_mode == PlayingMode.FOLLOWER
167
+ )
168
+ or (
169
+ self.previous_playing_mode == PlayingMode.FOLLOWER
170
+ and self.play_mode != PlayingMode.FOLLOWER
171
+ )
172
+ ):
173
+ self.bridge.device.controller()
174
+ self.previous_playing_mode = self.play_mode
142
175
 
143
176
  async def next(self) -> None:
144
177
  """Play the next song in the playlist."""
@@ -255,6 +288,14 @@ class LinkPlayPlayer:
255
288
  ):
256
289
  await self.bridge.request(LinkPlayCommand.SEEK.format(position))
257
290
 
291
+ async def set_audio_output_hw_mode(self, mode: AudioOutputHwMode) -> None:
292
+ """Set the audio hardware output."""
293
+ await self.bridge.request(LinkPlayCommand.AUDIO_OUTPUT_HW_MODE_SET.format(mode))
294
+
295
+ async def get_audio_output_hw_mode(self) -> None:
296
+ """Get the audio hardware output."""
297
+ await self.bridge.json_request(LinkPlayCommand.AUDIO_OUTPUT_HW_MODE)
298
+
258
299
  @property
259
300
  def muted(self) -> bool:
260
301
  """Returns if the player is muted."""
@@ -278,6 +319,13 @@ class LinkPlayPlayer:
278
319
  """Returns if the currently playing album."""
279
320
  return self.properties.get(PlayerAttribute.ALBUM, "")
280
321
 
322
+ @property
323
+ def album_art(self) -> str:
324
+ """Returns the url to the album art."""
325
+ return self.metainfo.get(MetaInfo.METADATA, {}).get(
326
+ MetaInfoMetaData.ALBUM_ART, ""
327
+ )
328
+
281
329
  @property
282
330
  def volume(self) -> int:
283
331
  """Returns the player volume, expressed in %."""
@@ -100,6 +100,9 @@ class LinkPlayCommand(StrEnum):
100
100
  PLAY_PRESET = "MCUKeyShortClick:{}"
101
101
  TIMESYNC = "timeSync:{}"
102
102
  WIIM_EQ_LOAD = "EQLoad:{}"
103
+ META_INFO = "getMetaInfo"
104
+ AUDIO_OUTPUT_HW_MODE_SET = "setAudioOutputHardwareMode:{}"
105
+ AUDIO_OUTPUT_HW_MODE = "getNewAudioOutputHardwareMode"
103
106
 
104
107
 
105
108
  class LinkPlayTcpUartCommand(StrEnum):
@@ -470,3 +473,48 @@ class MultiroomAttribute(StrEnum):
470
473
 
471
474
  def __repr__(self):
472
475
  return self.value
476
+
477
+
478
+ class MetaInfo(StrEnum):
479
+
480
+ METADATA = "metaData"
481
+
482
+ def __str__(self):
483
+ return self.value
484
+
485
+ def __repr__(self):
486
+ return self.value
487
+
488
+ class MetaInfoMetaData(StrEnum):
489
+ """Defines the metadata within the metainfo."""
490
+
491
+ ALBUM_TITLE = "album"
492
+ TRACK_TITLE = "title"
493
+ TRACK_SUBTITLE = "subtitle"
494
+ ALBUM_ART = "albumArtURI"
495
+ SAMPLE_RATE = "sampleRate"
496
+ BIT_DEPTH = "bitDepth"
497
+ BIT_RATE = "bitRate"
498
+ TRACK_ID = "trackId"
499
+
500
+ def __str__(self):
501
+ return self.value
502
+
503
+ def __repr__(self):
504
+ return self.value
505
+
506
+
507
+ class AudioOutputHwMode(StrEnum):
508
+ """Defines a output mode for the hardware."""
509
+ OPTICAL = "1"
510
+ LINE_OUT = "2"
511
+ COAXIAL = "3"
512
+ HEADPHONES = "4"
513
+
514
+ # Map between a play mode and how to activate the play mode
515
+ AUDIO_OUTPUT_HW_MODE_MAP: dict[AudioOutputHwMode, str] = { # case sensitive!
516
+ AudioOutputHwMode.OPTICAL: "optical",
517
+ AudioOutputHwMode.LINE_OUT: "line-out",
518
+ AudioOutputHwMode.COAXIAL: "co-axial",
519
+ AudioOutputHwMode.HEADPHONES: "headphones",
520
+ }
@@ -18,6 +18,16 @@ class LinkPlayController:
18
18
  self.bridges = []
19
19
  self.multirooms = []
20
20
 
21
+ def get_bridge_callback(self):
22
+ """Returns an async callback function for LinkPlayBridge."""
23
+
24
+ async def callback() -> None:
25
+ """Async callback function to handle events from a LinkPlayBridge."""
26
+ LOGGER.debug("Controller event received")
27
+ await self.discover_multirooms()
28
+
29
+ return callback
30
+
21
31
  async def discover_bridges(self) -> None:
22
32
  """Attempts to discover LinkPlay devices on the local network."""
23
33
 
@@ -46,6 +56,7 @@ class LinkPlayController:
46
56
  # Add bridge
47
57
  current_bridges = [bridge.device.uuid for bridge in self.bridges]
48
58
  if bridge_to_add.device.uuid not in current_bridges:
59
+ bridge_to_add.device.set_callback(self.get_bridge_callback())
49
60
  self.bridges.append(bridge_to_add)
50
61
 
51
62
  async def remove_bridge(self, bridge_to_remove: LinkPlayBridge) -> None:
@@ -80,6 +91,8 @@ class LinkPlayController:
80
91
  multiroom.leader.multiroom = None
81
92
  removed_multirooms.append(multiroom)
82
93
  except LinkPlayInvalidDataException as exc:
94
+ multiroom.leader.multiroom = None
95
+ removed_multirooms.append(multiroom)
83
96
  LOGGER.exception(exc)
84
97
 
85
98
  # Create new multirooms from new bridges
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python_linkplay
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: A Python Library for Seamless LinkPlay Device Control
5
5
  Author: Velleman Group nv
6
6
  License: MIT
@@ -1 +0,0 @@
1
- __version__ = '0.2.2'
File without changes