spotifywebapiPython 1.0.242__py3-none-any.whl

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 (138) hide show
  1. spotifywebapipython/__init__.py +35 -0
  2. spotifywebapipython/const.py +114 -0
  3. spotifywebapipython/docs/__init__.html +2156 -0
  4. spotifywebapipython/docs/codecopy.js +70 -0
  5. spotifywebapipython/docs/index.html +514 -0
  6. spotifywebapipython/docs/search.js +46 -0
  7. spotifywebapipython/docs/spotifywebapipython/const.html +636 -0
  8. spotifywebapipython/docs/spotifywebapipython/saappmessages.html +540 -0
  9. spotifywebapipython/docs/spotifywebapipython/sautils.html +620 -0
  10. spotifywebapipython/docs/spotifywebapipython/spotifyapierror.html +341 -0
  11. spotifywebapipython/docs/spotifywebapipython/spotifyapimessage.html +598 -0
  12. spotifywebapipython/docs/spotifywebapipython/spotifyauthtoken.html +612 -0
  13. spotifywebapipython/docs/spotifywebapipython/spotifyclient.html +16557 -0
  14. spotifywebapipython/docs/spotifywebapipython/spotifydiscovery.html +518 -0
  15. spotifywebapipython/docs/spotifywebapipython/spotifywebapiauthenticationerror.html +421 -0
  16. spotifywebapipython/docs/spotifywebapipython/spotifywebapierror.html +400 -0
  17. spotifywebapipython/docs/spotifywebapipython/spotifywebplayertoken.html +635 -0
  18. spotifywebapipython/docs/spotifywebapipython.ico +0 -0
  19. spotifywebapipython/docs/spotifywebapipython_OLD.ico +0 -0
  20. spotifywebapipython/models/__init__.py +121 -0
  21. spotifywebapipython/models/album.py +170 -0
  22. spotifywebapipython/models/albumpagesaved.py +85 -0
  23. spotifywebapipython/models/albumpagesimplified.py +88 -0
  24. spotifywebapipython/models/albumsaved.py +102 -0
  25. spotifywebapipython/models/albumsimplified.py +308 -0
  26. spotifywebapipython/models/artist.py +146 -0
  27. spotifywebapipython/models/artistinfo.py +282 -0
  28. spotifywebapipython/models/artistinfotourevent.py +127 -0
  29. spotifywebapipython/models/artistpage.py +70 -0
  30. spotifywebapipython/models/artistsimplified.py +163 -0
  31. spotifywebapipython/models/audiobook.py +87 -0
  32. spotifywebapipython/models/audiobookpagesimplified.py +88 -0
  33. spotifywebapipython/models/audiobooksimplified.py +362 -0
  34. spotifywebapipython/models/audiofeatures.py +333 -0
  35. spotifywebapipython/models/author.py +67 -0
  36. spotifywebapipython/models/category.py +168 -0
  37. spotifywebapipython/models/categorypage.py +70 -0
  38. spotifywebapipython/models/chapter.py +84 -0
  39. spotifywebapipython/models/chapterpagesimplified.py +88 -0
  40. spotifywebapipython/models/chaptersimplified.py +387 -0
  41. spotifywebapipython/models/context.py +113 -0
  42. spotifywebapipython/models/copyright.py +79 -0
  43. spotifywebapipython/models/device.py +301 -0
  44. spotifywebapipython/models/episode.py +84 -0
  45. spotifywebapipython/models/episodepagesaved.py +85 -0
  46. spotifywebapipython/models/episodepagesimplified.py +88 -0
  47. spotifywebapipython/models/episodesaved.py +99 -0
  48. spotifywebapipython/models/episodesimplified.py +370 -0
  49. spotifywebapipython/models/explicitcontent.py +80 -0
  50. spotifywebapipython/models/externalids.py +91 -0
  51. spotifywebapipython/models/externalurls.py +104 -0
  52. spotifywebapipython/models/followers.py +81 -0
  53. spotifywebapipython/models/imageobject.py +193 -0
  54. spotifywebapipython/models/imagevibrantcolors.py +182 -0
  55. spotifywebapipython/models/linkedfrom.py +165 -0
  56. spotifywebapipython/models/narrator.py +67 -0
  57. spotifywebapipython/models/owner.py +162 -0
  58. spotifywebapipython/models/pageobject.py +343 -0
  59. spotifywebapipython/models/playeractions.py +196 -0
  60. spotifywebapipython/models/playerlastplayedinfo.py +334 -0
  61. spotifywebapipython/models/playerplaystate.py +467 -0
  62. spotifywebapipython/models/playerqueueinfo.py +207 -0
  63. spotifywebapipython/models/playhistory.py +136 -0
  64. spotifywebapipython/models/playhistorypage.py +85 -0
  65. spotifywebapipython/models/playlist.py +125 -0
  66. spotifywebapipython/models/playlistpage.py +85 -0
  67. spotifywebapipython/models/playlistpagesimplified.py +107 -0
  68. spotifywebapipython/models/playlistsimplified.py +334 -0
  69. spotifywebapipython/models/playlisttrack.py +143 -0
  70. spotifywebapipython/models/playlisttracksummary.py +80 -0
  71. spotifywebapipython/models/recommendationseed.py +135 -0
  72. spotifywebapipython/models/restrictions.py +86 -0
  73. spotifywebapipython/models/resumepoint.py +79 -0
  74. spotifywebapipython/models/searchresponse.py +391 -0
  75. spotifywebapipython/models/searchresultbase.py +299 -0
  76. spotifywebapipython/models/show.py +87 -0
  77. spotifywebapipython/models/showpagesaved.py +85 -0
  78. spotifywebapipython/models/showpagesimplified.py +88 -0
  79. spotifywebapipython/models/showsaved.py +99 -0
  80. spotifywebapipython/models/showsimplified.py +320 -0
  81. spotifywebapipython/models/spotifyconnectdevice.py +321 -0
  82. spotifywebapipython/models/spotifyconnectdevices.py +441 -0
  83. spotifywebapipython/models/track.py +142 -0
  84. spotifywebapipython/models/trackpage.py +110 -0
  85. spotifywebapipython/models/trackpagesaved.py +85 -0
  86. spotifywebapipython/models/trackpagesimplified.py +88 -0
  87. spotifywebapipython/models/trackrecommendations.py +88 -0
  88. spotifywebapipython/models/tracksaved.py +99 -0
  89. spotifywebapipython/models/tracksimplified.py +398 -0
  90. spotifywebapipython/models/userprofile.py +184 -0
  91. spotifywebapipython/models/userprofilesimplified.py +206 -0
  92. spotifywebapipython/models/zeroconfdiscoveryresult.py +592 -0
  93. spotifywebapipython/models/zeroconfproperty.py +93 -0
  94. spotifywebapipython/oauthcli/__init__.py +7 -0
  95. spotifywebapipython/oauthcli/authclient.py +1229 -0
  96. spotifywebapipython/saappmessages.py +86 -0
  97. spotifywebapipython/sautils.py +285 -0
  98. spotifywebapipython/spotifyapierror.py +66 -0
  99. spotifywebapipython/spotifyapimessage.py +269 -0
  100. spotifywebapipython/spotifyauthtoken.py +264 -0
  101. spotifywebapipython/spotifyclient.py +17885 -0
  102. spotifywebapipython/spotifyconnect/__init__.py +19 -0
  103. spotifywebapipython/spotifyconnect/spotifyconnectdeviceeventargs.py +67 -0
  104. spotifywebapipython/spotifyconnect/spotifyconnectdirectorytask.py +2564 -0
  105. spotifywebapipython/spotifyconnect/spotifyconnectzeroconfcastapptask.py +467 -0
  106. spotifywebapipython/spotifyconnect/spotifyconnectzeroconfcastcontroller.py +701 -0
  107. spotifywebapipython/spotifyconnect/spotifyconnectzeroconfcastlistener.py +359 -0
  108. spotifywebapipython/spotifyconnect/spotifyconnectzeroconfcastmultizonelistener.py +212 -0
  109. spotifywebapipython/spotifyconnect/spotifyconnectzeroconfexceptions.py +20 -0
  110. spotifywebapipython/spotifyconnect/spotifyconnectzeroconflistener.py +335 -0
  111. spotifywebapipython/spotifydiscovery.py +473 -0
  112. spotifywebapipython/spotifymediatypes.py +53 -0
  113. spotifywebapipython/spotifywebapiauthenticationerror.py +122 -0
  114. spotifywebapipython/spotifywebapierror.py +106 -0
  115. spotifywebapipython/spotifywebplayertoken.py +743 -0
  116. spotifywebapipython/vibrant/__init__.py +17 -0
  117. spotifywebapipython/vibrant/generator.py +261 -0
  118. spotifywebapipython/vibrant/image.py +100 -0
  119. spotifywebapipython/vibrant/main.py +34 -0
  120. spotifywebapipython/vibrant/models.py +93 -0
  121. spotifywebapipython/vibrant/utils.py +56 -0
  122. spotifywebapipython/zeroconfapi/__init__.py +23 -0
  123. spotifywebapipython/zeroconfapi/authenticationtypes.py +38 -0
  124. spotifywebapipython/zeroconfapi/blobbuilder.py +370 -0
  125. spotifywebapipython/zeroconfapi/credentials.py +37 -0
  126. spotifywebapipython/zeroconfapi/cryptodiffiehellman.py +89 -0
  127. spotifywebapipython/zeroconfapi/helpers.py +64 -0
  128. spotifywebapipython/zeroconfapi/spotifyzeroconfapierror.py +108 -0
  129. spotifywebapipython/zeroconfapi/zeroconfconnect.py +1327 -0
  130. spotifywebapipython/zeroconfapi/zeroconfgetinfo.py +572 -0
  131. spotifywebapipython/zeroconfapi/zeroconfgetinfoalias.py +109 -0
  132. spotifywebapipython/zeroconfapi/zeroconfgetinfodrmmediaformat.py +90 -0
  133. spotifywebapipython/zeroconfapi/zeroconfresponse.py +195 -0
  134. spotifywebapipython-1.0.242.dist-info/LICENSE +22 -0
  135. spotifywebapipython-1.0.242.dist-info/METADATA +219 -0
  136. spotifywebapipython-1.0.242.dist-info/RECORD +138 -0
  137. spotifywebapipython-1.0.242.dist-info/WHEEL +5 -0
  138. spotifywebapipython-1.0.242.dist-info/top_level.txt +1 -0
@@ -0,0 +1,359 @@
1
+ # external package imports.
2
+ from pychromecast import CastInfo, HostServiceInfo
3
+ from pychromecast.discovery import AbstractCastListener
4
+ import threading
5
+ from uuid import UUID
6
+
7
+ # our package imports.
8
+ from spotifywebapipython.models import ZeroconfProperty, ZeroconfDiscoveryResult
9
+ # note - cannot reference `SpotifyConnectDirectoryTask` due to circular import!
10
+ # from .spotifyconnectdirectorytask import SpotifyConnectDirectoryTask
11
+
12
+ # get smartinspect logger reference; create a new session for this module name.
13
+ from smartinspectpython.siauto import SIAuto, SILevel, SISession, SIMethodParmListContext, SIColors
14
+ import logging
15
+
16
+ _logsi:SISession = SIAuto.Si.GetSession(__name__)
17
+ if (_logsi == None):
18
+ _logsi = SIAuto.Si.AddSession(__name__, True)
19
+ _logsi.SystemLogger = logging.getLogger(__name__)
20
+
21
+ ZEROCONF_SERVICETYPE_GOOGLECAST:str = "_googlecast._tcp.local."
22
+ """
23
+ Googlecast Zeroconf service type identifier (e.g. "_googlecast._tcp.local.").
24
+ """
25
+
26
+ # chromecast types supported.
27
+ CAST_TYPE_CHROMECAST = "cast"
28
+ """ Regular chromecast, supports video/audio """
29
+ CAST_TYPE_AUDIO = "audio"
30
+ """ Cast Audio device, supports only audio """
31
+ CAST_TYPE_GROUP = "group"
32
+ """ Cast Audio group device, supports only audio """
33
+ CAST_TYPE_NONE = None
34
+ """ cast_type not set; assume it supports audio """
35
+
36
+
37
+ class SpotifyConnectZeroconfCastListener(AbstractCastListener):
38
+ """
39
+ Google Chromecast Zeroconf Listener class.
40
+
41
+ Listens for Chromecast device connection updates for devices that
42
+ support Spotify Connect.
43
+ """
44
+
45
+ def __init__(
46
+ self,
47
+ parentDirectory,
48
+ zeroconf_RLock:threading.RLock,
49
+ ) -> None:
50
+ """
51
+ Initializes a new instance of the class.
52
+
53
+ Args:
54
+ parentDirectory (SpotifyConnectDirectoryTask):
55
+ Parent SpotifyConnectDirectoryTask instance.
56
+ zeroconf_RLock (threading.RLock):
57
+ Lock object used to enforce thread-safe updates.
58
+ """
59
+ # invoke base class method.
60
+ super().__init__()
61
+
62
+ # initialize storage.
63
+ self._ParentDirectory = parentDirectory
64
+ self._Zeroconf_RLock:threading.RLock = zeroconf_RLock
65
+
66
+
67
+ def _GetZeroconfDiscoveryResult(
68
+ self,
69
+ uuid:UUID,
70
+ serviceName:str,
71
+ castInfo_removed:CastInfo = None
72
+ ) -> ZeroconfDiscoveryResult:
73
+ """
74
+ Builds a `ZeroconfDiscoveryResult` instance from Zeroconf CastInfo data
75
+ when an add or update service state change takes place.
76
+
77
+ Args:
78
+ uuid (UUID):
79
+ The cast's uuid, which is the dictionary key to find the chromecast
80
+ metadata in CastBrowser.devices collection.
81
+ serviceName (str):
82
+ First known MDNS service name or host:port.
83
+ castInfo_removed (CastInfo):
84
+ CastInfo for the service to aid cleanup; only supplied for removal
85
+ requests since the CastBrowser.services collection no longer contains
86
+ a CastInfo object for the specified uuid.
87
+ """
88
+ result:ZeroconfDiscoveryResult = None
89
+ castInfoHost:str = None
90
+ castInfoPort:int = 0
91
+
92
+ try:
93
+
94
+ # is this an MDNSServiceInfo service record? we only want discovery results
95
+ # that contain MDNSServiceInfo service information.
96
+ if (serviceName.find(ZEROCONF_SERVICETYPE_GOOGLECAST) == -1):
97
+ _logsi.LogDebug("Chromecast Zeroconf discovery service notification: \"%s\" (%s)" % (serviceName, "ignored; not MDNSInfo"))
98
+ return None
99
+
100
+ # get chromecast info.
101
+ castInfo:CastInfo = castInfo_removed
102
+ if (castInfo is None):
103
+
104
+ # map the CastInfo object.
105
+ castInfo:CastInfo = self._ParentDirectory._CastBrowser.services[uuid]
106
+ castInfoHost = castInfo.host
107
+ castInfoPort = castInfo.port
108
+
109
+ # 20250628 commented out the ipv6 address resolution code.
110
+ # the `get_multizone_status` (in directorytask) could possibly return ipv6 info, with no ipv4 data!
111
+
112
+ # # find ipv4 host and port number.
113
+ # # if the base CastInfo object is ipv6, then find the ipv4 HostServiceInfo
114
+ # # service record and get it from there.
115
+ # if ((castInfoHost + "").find(":") != -1):
116
+
117
+ # # cast info host information is ipv6!
118
+ # # find the ipv4 host service info object.
119
+ # _logsi.LogDebug("Chromecast Zeroconf discovery service notification contained IPV6 HostServiceInfo data: \"%s\" (%s)" % (serviceName, "searching for IPV4 info"), colorValue=SIColors.Coral)
120
+ # castHostIpv4:HostServiceInfo = None
121
+ # for castSvc in castInfo.services:
122
+ # if (isinstance(castSvc, HostServiceInfo)):
123
+ # if ((castSvc.host + "").find(":") == -1):
124
+ # castInfoHost = castSvc.host
125
+ # castInfoPort = castSvc.port
126
+ # castHostIpv4 = castSvc
127
+ # break
128
+
129
+ # # if ipv4 host service info not found then we are done, as Spotify Connect
130
+ # # zeroconf requires an IPV4 address structure.
131
+ # if (castHostIpv4 is None):
132
+ # _logsi.LogDebug("Chromecast Zeroconf discovery service notification did not contain IPV4 HostServiceInfo service entry: \"%s\" (%s)" % (serviceName, "IPV6 HostServiceInfo may fail"), colorValue=SIColors.Coral)
133
+
134
+ else:
135
+
136
+ # set host and port number for remove request.
137
+ castInfoHost = castInfo.host
138
+ castInfoPort = castInfo.port
139
+
140
+ # trace.
141
+ _logsi.LogObject(SILevel.Debug, "Chromecast Zeroconf service details: \"%s\" (%s) (CastInfo object)" % (castInfo.friendly_name, serviceName), castInfo)
142
+
143
+ # only certain cast_type values support Spotify Connect.
144
+ castType:str = castInfo.cast_type
145
+ if (not (castType in [CAST_TYPE_AUDIO, CAST_TYPE_GROUP, CAST_TYPE_CHROMECAST, CAST_TYPE_NONE])):
146
+ _logsi.LogDebug("Chromecast device cast_type of \"%s\" is not supported; ignoring: \"%s\" (%s)" % (castType, castInfo.friendly_name, serviceName), colorValue=SIColors.Coral)
147
+ return None
148
+
149
+ # create new discovery result instance.
150
+ result:ZeroconfDiscoveryResult = ZeroconfDiscoveryResult()
151
+ result.DeviceName = castInfo.friendly_name
152
+ result.Domain = ".local"
153
+ result.HostIpAddresses = [castInfoHost]
154
+ result.HostIpPort = castInfoPort
155
+ result.HostTTL = 120
156
+ result.IsChromeCast = True
157
+ result.Key = str(uuid)
158
+ result.Name = serviceName
159
+ result.Priority = 0
160
+ result.OtherTTL = 4500
161
+ result.Server = serviceName
162
+ result.ServerKey = serviceName
163
+ result.ServiceType = ZEROCONF_SERVICETYPE_GOOGLECAST
164
+ result.Weight = 0
165
+ result.Properties.append(ZeroconfProperty("CPath", "/na"))
166
+ result.Properties.append(ZeroconfProperty("VERSION", "1.0"))
167
+ result.Properties.append(ZeroconfProperty("cast_type", castType))
168
+ result.SpotifyConnectCPath = "/na"
169
+ result.SpotifyConnectVersion = "1.0"
170
+ result.Id = "\"%s\" (%s:%s)" % (result.DeviceName, result.HostIpAddress, result.HostIpPort)
171
+
172
+ # trace.
173
+ _logsi.LogObject(SILevel.Debug, "Chromecast Zeroconf Discovery Result: %s (object)" % (result.Id), result, excludeNonPublic=True)
174
+
175
+ # return discovery result.
176
+ return result
177
+
178
+ except Exception as ex:
179
+
180
+ # trace.
181
+ _logsi.LogException("Could not load ZeroconfDiscoveryResult instance from Chromecast Zeroconf service details", ex, logToSystemLogger=False)
182
+
183
+ # ignore exception
184
+ return None
185
+
186
+
187
+ def add_cast(
188
+ self,
189
+ uuid:UUID,
190
+ serviceName:str
191
+ ) -> None:
192
+ """
193
+ Called when a new cast has been discovered.
194
+
195
+ Args:
196
+ uuid (UUID):
197
+ The cast's uuid, which is the dictionary key to find the chromecast
198
+ metadata in CastBrowser.devices collection.
199
+ serviceName (str):
200
+ First known MDNS service name or host:port.
201
+ """
202
+ # use lock, as multiple threads could be calling this method simultaneously.
203
+ with self._Zeroconf_RLock:
204
+
205
+ # copy passed parameters to thread-safe storage.
206
+ argsUuid:UUID = uuid
207
+ argsServiceName:str = serviceName
208
+
209
+ apiMethodName:str = "add_cast"
210
+ apiMethodParms:SIMethodParmListContext = None
211
+
212
+ try:
213
+
214
+ # trace.
215
+ apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
216
+ apiMethodParms.AppendKeyValue("uuid", argsUuid)
217
+ apiMethodParms.AppendKeyValue("serviceName", argsServiceName)
218
+ _logsi.LogMethodParmList(SILevel.Debug, "Chromecast Zeroconf discovery service notification: \"%s\" (%s)" % (argsServiceName, apiMethodName), apiMethodParms)
219
+
220
+ # build discovery result instance from service state information.
221
+ # if nothing returned, then don't bother!
222
+ result:ZeroconfDiscoveryResult = self._GetZeroconfDiscoveryResult(argsUuid, argsServiceName, None)
223
+ if (result is None):
224
+ return
225
+
226
+ # let the parent handle the event.
227
+ self._ParentDirectory.OnServiceInfoAddedUpdatedChromecast(result, argsUuid, argsServiceName, apiMethodName)
228
+
229
+ except Exception as ex:
230
+
231
+ # trace.
232
+ _logsi.LogException("Unhandled exception occured while processing Chromecast Zeroconf service browser ServiceStateChange notification (%s)" % (apiMethodName), ex, logToSystemLogger=False)
233
+
234
+ # ignore exception, as nothing can be done about it.
235
+
236
+ finally:
237
+
238
+ # trace.
239
+ _logsi.LeaveMethod(SILevel.Debug, apiMethodName)
240
+
241
+
242
+ def remove_cast(
243
+ self,
244
+ uuid:UUID,
245
+ serviceName:str,
246
+ castInfo:CastInfo
247
+ ) -> None:
248
+ """
249
+ Called when a cast has been lost (MDNS info expired or host down).
250
+
251
+ Args:
252
+ uuid (UUID):
253
+ The cast's uuid, which is the dictionary key to find the chromecast
254
+ metadata in CastBrowser.devices collection.
255
+ serviceName (str):
256
+ Last valid MDNS service name or host:port.
257
+ castInfo (CastInfo):
258
+ CastInfo for the service to aid cleanup.
259
+ """
260
+ # use lock, as multiple threads could be calling this method simultaneously.
261
+ with self._Zeroconf_RLock:
262
+
263
+ # copy passed parameters to thread-safe storage.
264
+ argsUuid:UUID = uuid
265
+ argsServiceName:str = serviceName
266
+ argsCastInfo:CastInfo = castInfo
267
+
268
+ apiMethodName:str = "remove_cast"
269
+ apiMethodParms:SIMethodParmListContext = None
270
+
271
+ try:
272
+
273
+ # trace.
274
+ apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
275
+ apiMethodParms.AppendKeyValue("uuid", argsUuid)
276
+ apiMethodParms.AppendKeyValue("serviceName", argsServiceName)
277
+ _logsi.LogMethodParmList(SILevel.Debug, "Chromecast Zeroconf discovery service notification: \"%s\" (%s)" % (argsServiceName, apiMethodName), apiMethodParms)
278
+
279
+ # for service removal notifications, there will be no zeroconf service info.
280
+ # build discovery result instance from service state information.
281
+ # if nothing returned, then it's ok - build a minimal discovery instance that
282
+ # will contain the necessary details to remove the device entry.
283
+ result:ZeroconfDiscoveryResult = self._GetZeroconfDiscoveryResult(argsUuid, argsServiceName, argsCastInfo)
284
+ if (result is None):
285
+ result = ZeroconfDiscoveryResult()
286
+ result.DeviceName = castInfo.friendly_name
287
+ result.Key = str(uuid)
288
+ result.Name = serviceName
289
+ result.ServiceType = ZEROCONF_SERVICETYPE_GOOGLECAST
290
+
291
+ # let the parent handle the event.
292
+ self._ParentDirectory.OnServiceInfoRemovedChromecast(result, argsUuid, argsServiceName, argsCastInfo)
293
+
294
+ except Exception as ex:
295
+
296
+ # trace.
297
+ _logsi.LogException("Unhandled exception occured while processing Chromecast Zeroconf service browser ServiceStateChange notification (%s)" % (apiMethodName), ex, logToSystemLogger=False)
298
+
299
+ # ignore exception, as nothing can be done about it.
300
+
301
+ finally:
302
+
303
+ # trace.
304
+ _logsi.LeaveMethod(SILevel.Debug, apiMethodName)
305
+
306
+
307
+ def update_cast(
308
+ self,
309
+ uuid:UUID,
310
+ serviceName:str
311
+ ) -> None:
312
+ """
313
+ Called when a cast has been updated (MDNS info renewed or changed).
314
+
315
+ Args:
316
+ uuid (UUID):
317
+ The cast's uuid, which is the dictionary key to find the chromecast
318
+ metadata in CastBrowser.devices collection.
319
+ serviceName (str):
320
+ MDNS service name or host:port.
321
+ """
322
+ # use lock, as multiple threads could be calling this method simultaneously.
323
+ with self._Zeroconf_RLock:
324
+
325
+ # copy passed parameters to thread-safe storage.
326
+ argsUuid:UUID = uuid
327
+ argsServiceName:str = serviceName
328
+
329
+ apiMethodName:str = "update_cast"
330
+ apiMethodParms:SIMethodParmListContext = None
331
+
332
+ try:
333
+
334
+ # trace.
335
+ apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
336
+ apiMethodParms.AppendKeyValue("uuid", argsUuid)
337
+ apiMethodParms.AppendKeyValue("serviceName", argsServiceName)
338
+ _logsi.LogMethodParmList(SILevel.Debug, "Chromecast Zeroconf discovery service notification: \"%s\" (%s)" % (serviceName, apiMethodName), apiMethodParms)
339
+
340
+ # build discovery result instance from service state information.
341
+ # if nothing returned, then don't bother!
342
+ result:ZeroconfDiscoveryResult = self._GetZeroconfDiscoveryResult(argsUuid, argsServiceName, None)
343
+ if (result is None):
344
+ return
345
+
346
+ # let the parent handle the event.
347
+ self._ParentDirectory.OnServiceInfoAddedUpdatedChromecast(result, argsUuid, argsServiceName, apiMethodName)
348
+
349
+ except Exception as ex:
350
+
351
+ # trace.
352
+ _logsi.LogException("Unhandled exception occured while processing Chromecast Zeroconf service browser ServiceStateChange notification (%s)" % (apiMethodName), ex, logToSystemLogger=False)
353
+
354
+ # ignore exception, as nothing can be done about it.
355
+
356
+ finally:
357
+
358
+ # trace.
359
+ _logsi.LeaveMethod(SILevel.Debug, apiMethodName)
@@ -0,0 +1,212 @@
1
+ # external package imports.
2
+ from pychromecast.controllers.multizone import MultizoneController, MultiZoneControllerListener
3
+ from pychromecast.dial import MultizoneStatus, get_multizone_status
4
+ from pychromecast import Chromecast
5
+ import pychromecast
6
+ import threading
7
+ from uuid import UUID
8
+ from zeroconf import Zeroconf
9
+
10
+ # our package imports.
11
+ from spotifywebapipython.models import SpotifyConnectDevice, ZeroconfDiscoveryResult
12
+ # note - cannot reference `SpotifyConnectDirectoryTask` due to circular import!
13
+ # from .spotifyconnectdirectorytask import SpotifyConnectDirectoryTask
14
+
15
+ # get smartinspect logger reference; create a new session for this module name.
16
+ from smartinspectpython.siauto import SIAuto, SILevel, SISession, SIMethodParmListContext, SIColors
17
+ import logging
18
+
19
+ _logsi:SISession = SIAuto.Si.GetSession(__name__)
20
+ if (_logsi == None):
21
+ _logsi = SIAuto.Si.AddSession(__name__, True)
22
+ _logsi.SystemLogger = logging.getLogger(__name__)
23
+
24
+
25
+ class SpotifyConnectZeroconfCastMultiZoneListener(MultiZoneControllerListener):
26
+ """
27
+ Google Chromecast Zeroconf MultiZone Listener class.
28
+
29
+ Listens for Chromecast device multi-zone status updates for devices that
30
+ support multi-zone / group functionality.
31
+ """
32
+
33
+ def __init__(
34
+ self,
35
+ parentDirectory,
36
+ zeroconfInstance:Zeroconf,
37
+ zeroconf_RLock:threading.RLock,
38
+ castMultizoneController:MultizoneController,
39
+ castDevice:Chromecast,
40
+ ) -> None:
41
+ """
42
+ Initializes a new instance of the class.
43
+
44
+ Args:
45
+ parentDirectory (SpotifyConnectDirectoryTask):
46
+ Parent SpotifyConnectDirectoryTask instance.
47
+ zeroconfInstance (Zeroconf):
48
+ Zeroconf instance used to query zeroconf.
49
+ zeroconf_RLock (threading.RLock):
50
+ Lock object used to enforce thread-safe updates.
51
+ castMultizoneController (MultizoneController)
52
+ MultizoneController parent instance.
53
+ castDevice (Chromecast):
54
+ Chromecast device object.
55
+ """
56
+ # invoke base class method.
57
+ super().__init__()
58
+
59
+ # initialize storage.
60
+ self._CastMultizoneController:MultizoneController = castMultizoneController
61
+ self._CastDevice = castDevice
62
+ self._ParentDirectory = parentDirectory
63
+ self._ZeroconfInstance:Zeroconf = zeroconfInstance
64
+ self._Zeroconf_RLock:threading.RLock = zeroconf_RLock
65
+
66
+
67
+ def multizone_member_added(
68
+ self,
69
+ group_uuid:str,
70
+ ) -> None:
71
+ """
72
+ Called when cast has been added to group identified by group_uuid.
73
+ """
74
+ # use lock, as multiple threads could be calling this method simultaneously.
75
+ with self._Zeroconf_RLock:
76
+
77
+ # copy passed parameters to thread-safe storage.
78
+ argsGroupUuid:UUID = group_uuid
79
+
80
+ apiMethodName:str = "multizone_member_added"
81
+ apiMethodParms:SIMethodParmListContext = None
82
+
83
+ try:
84
+
85
+ # trace.
86
+ apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
87
+ apiMethodParms.AppendKeyValue("group_uuid", argsGroupUuid)
88
+ _logsi.LogMethodParmList(SILevel.Debug, "Chromecast multizone group notification was received for group \"%s\"" % (argsGroupUuid), apiMethodParms, colorValue=SIColors.Lavender)
89
+
90
+ # get device instance.
91
+ scDevice:SpotifyConnectDevice = self._ParentDirectory._SpotifyConnectDevices.GetDeviceByDiscoveryKey(str(argsGroupUuid))
92
+ if (scDevice is None):
93
+
94
+ # trace.
95
+ _logsi.LogVerbose("Could not find SpotifyConnectDevices instance for Cast Group discovery key: \"%s\"" % (str(argsGroupUuid)), colorValue=SIColors.Lavender)
96
+
97
+ else:
98
+
99
+ # trace.
100
+ _logsi.LogVerbose("Chromecast member device %s was added to multizone group \"%s\"" % (scDevice.Title, self._CastDevice.cast_info.friendly_name), colorValue=SIColors.Lavender)
101
+
102
+ # dump the multizone status for the newly added member.
103
+ castMultiZoneStatus:MultizoneStatus = get_multizone_status(scDevice.DiscoveryResult.HostIpAddress, self._CastDevice.cast_info.services, self._ZeroconfInstance, 5)
104
+ _logsi.LogObject(SILevel.Verbose, "Chromecast member device multizone status: %s [%s] (get_multizone_status)" % (scDevice.Title, scDevice.DiscoveryResult.HostIpTitle), castMultiZoneStatus, colorValue=SIColors.Lavender)
105
+
106
+ except Exception as ex:
107
+
108
+ # trace.
109
+ _logsi.LogException("Unhandled exception occured while processing Chromecast MultiZoneController notification (%s)" % (apiMethodName), ex, logToSystemLogger=False)
110
+
111
+ # ignore exception, as nothing can be done about it.
112
+
113
+ finally:
114
+
115
+ # trace.
116
+ _logsi.LeaveMethod(SILevel.Debug, apiMethodName)
117
+
118
+
119
+ def multizone_member_removed(
120
+ self,
121
+ group_uuid,
122
+ ) -> None:
123
+ """
124
+ Called when cast has been removed from group identified by group_uuid.
125
+ """
126
+ # use lock, as multiple threads could be calling this method simultaneously.
127
+ with self._Zeroconf_RLock:
128
+
129
+ # copy passed parameters to thread-safe storage.
130
+ argsGroupUuid:UUID = group_uuid
131
+
132
+ apiMethodName:str = "multizone_member_removed"
133
+ apiMethodParms:SIMethodParmListContext = None
134
+
135
+ try:
136
+
137
+ # trace.
138
+ apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
139
+ apiMethodParms.AppendKeyValue("group_uuid", argsGroupUuid)
140
+ _logsi.LogMethodParmList(SILevel.Debug, "Chromecast multizone group notification was received for group \"%s\"" % (argsGroupUuid), apiMethodParms, colorValue=SIColors.Lavender)
141
+
142
+ # get device instance.
143
+ scDevice:SpotifyConnectDevice = self._ParentDirectory._SpotifyConnectDevices.GetDeviceByDiscoveryKey(str(argsGroupUuid))
144
+ if (scDevice is None):
145
+
146
+ # trace.
147
+ _logsi.LogVerbose("Could not find SpotifyConnectDevices instance for Cast Group discovery key: \"%s\"" % (str(argsGroupUuid)), colorValue=SIColors.Lavender)
148
+
149
+ else:
150
+
151
+ # trace.
152
+ _logsi.LogVerbose("Chromecast member device %s was removed from multizone group \"%s\"" % (scDevice.Title, self._CastDevice.cast_info.friendly_name), colorValue=SIColors.Lavender)
153
+
154
+ except Exception as ex:
155
+
156
+ # trace.
157
+ _logsi.LogException("Unhandled exception occured while processing Chromecast MultiZoneController notification (%s)" % (apiMethodName), ex, logToSystemLogger=False)
158
+
159
+ # ignore exception, as nothing can be done about it.
160
+
161
+ finally:
162
+
163
+ # trace.
164
+ _logsi.LeaveMethod(SILevel.Debug, apiMethodName)
165
+
166
+
167
+ def multizone_status_received(
168
+ self,
169
+ ) -> None:
170
+ """
171
+ Called when Multizone status has been updated.
172
+ """
173
+ # use lock, as multiple threads could be calling this method simultaneously.
174
+ with self._Zeroconf_RLock:
175
+
176
+ # copy passed parameters to thread-safe storage.
177
+ # n/a
178
+
179
+ apiMethodName:str = "multizone_status_received"
180
+ apiMethodParms:SIMethodParmListContext = None
181
+
182
+ try:
183
+
184
+ # trace.
185
+ apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
186
+ _logsi.LogMethodParmList(SILevel.Debug, "Chromecast multizone group status has been updated", apiMethodParms, colorValue=SIColors.Lavender)
187
+
188
+ # trace.
189
+ if (_logsi.IsOn(SILevel.Verbose)):
190
+
191
+ # build member list with friendly names.
192
+ result:list[str] = []
193
+ member:str = None
194
+ for member in self._CastMultizoneController.members:
195
+ scDevice:SpotifyConnectDevice = self._ParentDirectory._SpotifyConnectDevices.GetDeviceByDiscoveryKey(member)
196
+ if (scDevice is None):
197
+ result.append(member)
198
+ else:
199
+ result.append(scDevice.Title)
200
+ _logsi.LogArray(SILevel.Verbose, "Chromecast multizone group \"%s\" members (count=%d)" % (self._CastDevice.cast_info.friendly_name, len(result)), result, colorValue=SIColors.Lavender)
201
+
202
+ except Exception as ex:
203
+
204
+ # trace.
205
+ _logsi.LogException("Unhandled exception occured while processing Chromecast MultiZoneController notification (%s)" % (apiMethodName), ex, logToSystemLogger=False)
206
+
207
+ # ignore exception, as nothing can be done about it.
208
+
209
+ finally:
210
+
211
+ # trace.
212
+ _logsi.LeaveMethod(SILevel.Debug, apiMethodName)
@@ -0,0 +1,20 @@
1
+ # our package imports.
2
+ from spotifywebapipython import SpotifyApiError
3
+
4
+
5
+ class SpotifyConnectZeroconfLaunchError(Exception):
6
+ """
7
+ Exception raised when a cast app fails to launch.
8
+ """
9
+
10
+
11
+ class SpotifyConnectZeroconfPlaybackTransferError(Exception):
12
+ """
13
+ Exception raised when a cast app fails to receive transfer of playback control.
14
+ """
15
+
16
+
17
+ class SpotifyConnectDeviceNotFound(SpotifyApiError):
18
+ """
19
+ Exception raised when a Spotify Connect device could not be resolved.
20
+ """