spotifywebapiPython 1.0.147__py3-none-any.whl → 1.0.149__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.

Potentially problematic release.


This version of spotifywebapiPython might be problematic. Click here for more details.

Files changed (38) hide show
  1. {spotifywebapiPython-1.0.147.dist-info → spotifywebapiPython-1.0.149.dist-info}/METADATA +5 -9
  2. {spotifywebapiPython-1.0.147.dist-info → spotifywebapiPython-1.0.149.dist-info}/RECORD +38 -29
  3. spotifywebapipython/__init__.py +0 -2
  4. spotifywebapipython/const.py +1 -1
  5. spotifywebapipython/docs/__init__.html +25 -4
  6. spotifywebapipython/docs/index.html +26 -2
  7. spotifywebapipython/docs/search.js +1 -1
  8. spotifywebapipython/docs/spotifywebapipython/const.html +3 -3
  9. spotifywebapipython/docs/spotifywebapipython/saappmessages.html +2 -2
  10. spotifywebapipython/docs/spotifywebapipython/sautils.html +8 -8
  11. spotifywebapipython/docs/spotifywebapipython/spotifyapierror.html +2 -2
  12. spotifywebapipython/docs/spotifywebapipython/spotifyapimessage.html +2 -2
  13. spotifywebapipython/docs/spotifywebapipython/spotifyauthtoken.html +2 -2
  14. spotifywebapipython/docs/spotifywebapipython/spotifyclient.html +514 -741
  15. spotifywebapipython/docs/spotifywebapipython/spotifydiscovery.html +3 -3
  16. spotifywebapipython/docs/spotifywebapipython/spotifywebapiauthenticationerror.html +2 -2
  17. spotifywebapipython/docs/spotifywebapipython/spotifywebapierror.html +2 -2
  18. spotifywebapipython/docs/spotifywebapipython/spotifywebplayertoken.html +466 -0
  19. spotifywebapipython/models/spotifyconnectdevice.py +84 -4
  20. spotifywebapipython/models/spotifyconnectdevices.py +16 -307
  21. spotifywebapipython/models/zeroconfdiscoveryresult.py +0 -41
  22. spotifywebapipython/sautils.py +0 -1
  23. spotifywebapipython/spotifyclient.py +1384 -1835
  24. spotifywebapipython/spotifyconnect/__init__.py +19 -0
  25. spotifywebapipython/{spotifyconnectdeviceeventargs.py → spotifyconnect/spotifyconnectdeviceeventargs.py} +1 -1
  26. spotifywebapipython/spotifyconnect/spotifyconnectdirectorytask.py +1967 -0
  27. spotifywebapipython/spotifyconnect/spotifyconnectzeroconfcastapptask.py +336 -0
  28. spotifywebapipython/spotifyconnect/spotifyconnectzeroconfcastcontroller.py +609 -0
  29. spotifywebapipython/spotifyconnect/spotifyconnectzeroconfcastlistener.py +316 -0
  30. spotifywebapipython/spotifyconnect/spotifyconnectzeroconfexceptions.py +10 -0
  31. spotifywebapipython/spotifyconnect/spotifyconnectzeroconflistener.py +335 -0
  32. spotifywebapipython/spotifydiscovery.py +0 -1
  33. spotifywebapipython/spotifywebplayertoken.py +344 -0
  34. spotifywebapipython/zeroconfapi/zeroconfgetinfo.py +4 -52
  35. spotifywebapipython/zeroconfapi/zeroconfresponse.py +8 -0
  36. {spotifywebapiPython-1.0.147.dist-info → spotifywebapiPython-1.0.149.dist-info}/LICENSE +0 -0
  37. {spotifywebapiPython-1.0.147.dist-info → spotifywebapiPython-1.0.149.dist-info}/WHEEL +0 -0
  38. {spotifywebapiPython-1.0.147.dist-info → spotifywebapiPython-1.0.149.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,336 @@
1
+ # external package imports.
2
+ from pychromecast import Chromecast
3
+ import threading
4
+ import time
5
+
6
+ # our package imports.
7
+ from .spotifyconnectzeroconfcastcontroller import SpotifyConnectZeroconfCastController
8
+ from .spotifyconnectzeroconfexceptions import SpotifyConnectZeroconfPlaybackTransferError
9
+ #from spotifywebapipython import SpotifyClient
10
+ from spotifywebapipython.zeroconfapi import ZeroconfGetInfo, ZeroconfResponse
11
+ from spotifywebapipython.spotifywebplayertoken import SpotifyWebPlayerToken
12
+
13
+ # get smartinspect logger reference; create a new session for this module name.
14
+ from smartinspectpython.siauto import SIAuto, SILevel, SISession
15
+ import logging
16
+
17
+ _logsi:SISession = SIAuto.Si.GetSession(__name__)
18
+ if (_logsi == None):
19
+ _logsi = SIAuto.Si.AddSession(__name__, True)
20
+ _logsi.SystemLogger = logging.getLogger(__name__)
21
+
22
+
23
+ class SpotifyConnectZeroconfCastAppTask(threading.Thread):
24
+ """
25
+ Spotify Connect Zeroconf Cast Application class.
26
+
27
+ Represents the Cast Application that is started on the Chromecast device
28
+ when a Spotify Connect connection request is made.
29
+ """
30
+
31
+ def __init__(
32
+ self,
33
+ castDevice:Chromecast,
34
+ spotifyClientInstance, # :SpotifyClient,
35
+ getInfoResponseReceivedCallback,
36
+ zeroconfResponseReceivedCallback,
37
+ transferPlayback:bool=False,
38
+ ) -> None:
39
+ """
40
+ Initializes a new instance of the class.
41
+
42
+ Args:
43
+ castDevice (Chromecast):
44
+ Chromecast device target.
45
+ spotifyClientInstance (SpotifyClient):
46
+ SpotifyClient instance used to transfer playback to the Chromecast device.
47
+ getInfoResponseReceivedCallback (function):
48
+ Function to call when a Spotify Connect GetInfoResponse data structure
49
+ has been received from the cast device.
50
+ zeroconfResponseReceivedCallback (function):
51
+ Function to call when a Spotify Connect ZeroconfResponse data structure
52
+ has been received from the cast device.
53
+ transferPlayback (bool):
54
+ True to transfer playback to the device; otherwise, False to just activate
55
+ the Spotify Cast App on the device.
56
+ """
57
+ # invoke base class method.
58
+ super().__init__()
59
+
60
+ # note that the initialization method executes in the parent thread;
61
+ # the code in the `run` method executes on a child (daemon) thread.
62
+
63
+ # set thread name before we start logging.
64
+ self.name = "Spotify Connect Zeroconf Cast App Task: \"%s\"" % castDevice.name
65
+
66
+ # trace.
67
+ _logsi.LogVerbose("%s - Initializing storage" % (self.name))
68
+
69
+ # initialize storage.
70
+ self._CastDevice:Chromecast = castDevice
71
+ self._GetInfoResponseReceivedCallback = getInfoResponseReceivedCallback
72
+ self._IsStopRequested:bool = False
73
+ self._SpotifyConnectZeroconfCastController:SpotifyConnectZeroconfCastController = None
74
+ self._SpotifyClientInstance = spotifyClientInstance
75
+ self._TransferPlayback:bool = transferPlayback
76
+ self._ZeroconfResponseReceivedCallback = zeroconfResponseReceivedCallback
77
+
78
+
79
+ @property
80
+ def CastDevice(self) -> Chromecast:
81
+ """
82
+ Returns the Chromecast device instance if one has been initialized;
83
+ otherwise, None.
84
+ """
85
+ return self._CastDevice
86
+
87
+
88
+ @property
89
+ def IsStopRequested(self) -> bool:
90
+ """
91
+ Indicator used to denote the task has been asked to stop by the main thread.
92
+ """
93
+ return self._IsStopRequested
94
+
95
+ @IsStopRequested.setter
96
+ def IsStopRequested(self, value:bool):
97
+ """
98
+ Sets the IsStopRequested property value.
99
+ """
100
+ if isinstance(value, bool):
101
+ self._IsStopRequested = value
102
+
103
+
104
+ @property
105
+ def SpotifyClientInstance(self) -> object: # SpotifyClient:
106
+ """
107
+ SpotifyClient instance used to transfer playback to the Chromecast device.
108
+ """
109
+ return self._SpotifyClientInstance
110
+
111
+
112
+ @property
113
+ def TransferPlayback(self) -> bool:
114
+ """
115
+ True to transfer playback to the device;
116
+ otherwise, False to just activate the Spotify Cast App on the device.
117
+ """
118
+ return self._TransferPlayback
119
+
120
+
121
+ @property
122
+ def ZeroconfInfo(self) -> ZeroconfGetInfo:
123
+ """
124
+ Returns the Chromecast Spotify Connect getinfo response, if the Spotify
125
+ application is running on the Chromecast devive; otherwise, None.
126
+ """
127
+ if (self._SpotifyConnectZeroconfCastController is None):
128
+ return None
129
+ return self._SpotifyConnectZeroconfCastController.zeroconfGetInfo
130
+
131
+
132
+ @property
133
+ def ZeroconfResponseObject(self) -> ZeroconfResponse:
134
+ """
135
+ Returns the Chromecast Spotify Connect zeroconf response, if the Spotify
136
+ application returned one; otherwise, None.
137
+ """
138
+ if (self._SpotifyConnectZeroconfCastController is None):
139
+ return None
140
+ return self._SpotifyConnectZeroconfCastController.zeroconfResponse
141
+
142
+
143
+ def _CallGetInfoResponseReceivedCallback(self) -> None:
144
+ """
145
+ If defined, call the callback to process the zeroconf getInfo response from the cast app task.
146
+ """
147
+ # was a GetInfoResponse callback defined?
148
+ if (self._GetInfoResponseReceivedCallback):
149
+
150
+ try:
151
+
152
+ # trace.
153
+ _logsi.LogVerbose("%s - Zeroconf getInfoResponse received; Spotify Connect info: \"%s\" (%s)" % (self.name, self.ZeroconfInfo.RemoteName, self.ZeroconfInfo.DeviceId))
154
+
155
+ # invoke callback to process the GetInfoResponse data.
156
+ self._GetInfoResponseReceivedCallback(self._CastDevice.uuid, self.ZeroconfInfo)
157
+
158
+ except Exception as ex:
159
+
160
+ # trace.
161
+ _logsi.LogException("An unhandled exception occured in GetInfoResponseReceivedCallback: %s" % (str(ex)), ex, logToSystemLogger=False)
162
+ #ignore exceptions, as there is nothing we can do about them!
163
+
164
+
165
+
166
+ def _CallZeroconfResponseReceivedCallback(self) -> None:
167
+ """
168
+ If defined, call the callback to process the various zeroconf responses from the cast app task.
169
+ """
170
+ # was a ZeroconfResponse callback defined?
171
+ if (self._ZeroconfResponseReceivedCallback):
172
+
173
+ try:
174
+
175
+ # trace.
176
+ _logsi.LogVerbose("%s - Zeroconf \"%s\" received; Spotify Connect response: \"%s\" (%s)" % (self.name, self.ZeroconfResponseObject.ResponseSource, self.ZeroconfResponseObject.StatusString, self.ZeroconfResponseObject.Status))
177
+
178
+ # invoke callback to process the ZeroconfResponse data.
179
+ self._ZeroconfResponseReceivedCallback(self._CastDevice.uuid, self.ZeroconfResponseObject)
180
+
181
+ except Exception as ex:
182
+
183
+ # trace.
184
+ _logsi.LogException("An unhandled exception occured in ZeroconfResponseReceivedCallback: %s" % (str(ex)), ex, logToSystemLogger=False)
185
+ #ignore exceptions, as there is nothing we can do about them!
186
+
187
+
188
+ def run(self):
189
+ """
190
+ The task to perform on a seperate thread.
191
+ """
192
+ try:
193
+
194
+ # trace.
195
+ _logsi.LogVerbose("%s - Starting thread RUN / TASK method" % (self.name))
196
+ _logsi.LogThread(SILevel.Debug, "%s - Thread information" % (self.name), self)
197
+
198
+ # start the connection worker thread, if needed.
199
+ # failure to do this will result in the following exception being thrown when
200
+ # the `launch_app()` method is called:
201
+ # - `pychromecast.error.NotConnected: Chromecast unknown:8009 is connecting...`
202
+ if (self._CastDevice.is_idle):
203
+ if (self._CastDevice.socket_client is not None) and (self._CastDevice.socket_client.first_connection):
204
+ _logsi.LogVerbose("%s - Starting Chromecast connection worker thread for loginId \"%s\"" % (self.name, self.SpotifyClientInstance.SpotifyConnectLoginId))
205
+ self._CastDevice.start()
206
+ self._CastDevice.wait(10)
207
+
208
+ # get spotify chromecast app access token info from spotify web player cookie credentials.
209
+ _logsi.LogVerbose("%s - Converting Spotify Web Player cookie credentials to an access token for loginId \"%s\"" % (self.name, self.SpotifyClientInstance.SpotifyConnectLoginId))
210
+ tokenWP = SpotifyWebPlayerToken(profileId=self.SpotifyClientInstance.SpotifyConnectLoginId,
211
+ tokenStorageDir=self.SpotifyClientInstance.TokenStorageDir,
212
+ tokenStorageFile=self.SpotifyClientInstance.TokenStorageFile)
213
+
214
+ # launch spotify chromecast app on the device, passing it the spotify web player access token info.
215
+ _logsi.LogVerbose("%s - Launching Spotify Chromecast App for loginId \"%s\"" % (self.name, self.SpotifyClientInstance.SpotifyConnectLoginId))
216
+ self._SpotifyConnectZeroconfCastController = SpotifyConnectZeroconfCastController(self._CastDevice, tokenWP.AccessToken, tokenWP.ExpiresAt)
217
+ self._CastDevice.register_handler(self._SpotifyConnectZeroconfCastController)
218
+ self._SpotifyConnectZeroconfCastController.launch_app(10)
219
+
220
+ # at this point the cast app should be either fully launched, or an error
221
+ # occured while trying to launch.
222
+
223
+ # was a ZeroconfResponse callback defined?
224
+ if (self._ZeroconfResponseReceivedCallback):
225
+
226
+ try:
227
+
228
+ # at this point, the Spotify Connect `addUser` has been made and the results populated.
229
+ _logsi.LogVerbose("%s - Zeroconf ZeroconfResponse received; Spotify Connect response: \"%s\" (%s)" % (self.name, self.ZeroconfResponseObject.StatusString, self.ZeroconfResponseObject.ResponseSource))
230
+
231
+ # invoke callback to process the ZeroconfResponse data.
232
+ self._ZeroconfResponseReceivedCallback(self._CastDevice.uuid, self.ZeroconfResponseObject)
233
+
234
+ except Exception as ex:
235
+
236
+ # trace.
237
+ _logsi.LogException("An unhandled exception occured in ZeroconfResponseReceivedCallback: %s" % (str(ex)), ex, logToSystemLogger=False)
238
+ #ignore exceptions, as there is nothing we can do about them!
239
+
240
+ # check for launch or getInfoError errors.
241
+ if (not self._SpotifyConnectZeroconfCastController.isLaunched) and (not self._SpotifyConnectZeroconfCastController.isAddUserError):
242
+ self._CallZeroconfResponseReceivedCallback()
243
+ raise ValueError("%s - Failed to launch spotify controller due to timeout" % (self.name))
244
+
245
+ # check for addUserError errors.
246
+ if (not self._SpotifyConnectZeroconfCastController.isLaunched) and (self._SpotifyConnectZeroconfCastController.isAddUserError):
247
+ self._CallZeroconfResponseReceivedCallback()
248
+ raise ValueError("%s - Failed to launch spotify controller due to credentials error" % (self.name))
249
+
250
+ # at this point the cast app launch was successful; the following steps were performed:
251
+ # - issues a Spotify Connect Zeroconf `getInfo` request to retrieve info about the device.
252
+ # - waits for a `getInfoResponse` message and processes the results.
253
+ # - issues a Spotify Connect Zeroconf `addUser` request to login the user to Spotify Connect.
254
+ # - waits for a `addUserResponse` (or `addUserError`) message and processes the results.
255
+
256
+ # at this point, the Spotify Connect `getInfo` has been made and the results
257
+ # populated; note that `getInfo` results are the same BEFORE the call to `addUser`
258
+ # as well as AFTER the call to `addUser` (no need to call `getInfo` again AFTER addUser).
259
+
260
+ # call the callback to process the getInfoResponse.
261
+ self._CallGetInfoResponseReceivedCallback()
262
+
263
+ # call the callback to process the addUserResponse.
264
+ self._CallZeroconfResponseReceivedCallback()
265
+
266
+ # trace.
267
+ _logsi.LogVerbose("%s - User is logged in to Spotify Cast App; waiting for transfer playback ..." % (self.name))
268
+
269
+ # was transfer playback selected?
270
+ if (self.TransferPlayback == True):
271
+
272
+ # transfer playback to the Chromecast device.
273
+ _logsi.LogVerbose("%s - Transferring playback for loginId \"%s\"" % (self.name, self.SpotifyClientInstance.SpotifyConnectLoginId))
274
+ self.SpotifyClientInstance.PlayerTransferPlayback(self.ZeroconfInfo.DeviceId, play=True, refreshDeviceList=False)
275
+
276
+ # the cast device will receive a Chromecast message of payload type `transferSuccess`
277
+ # on successful transfer of playback; this will set the `isPlaybackTransferred` flag to True.
278
+
279
+ # wait for the launched spotify app to receive transfer playback control, either
280
+ # through the `PlayerTransferPlayback` executed above OR from another Spotify Connect
281
+ # capable player.
282
+ timeout:int = 20
283
+ counter = 0
284
+ while counter < (timeout + 1):
285
+ if (self._SpotifyConnectZeroconfCastController.waitPlaybackTransfer.wait(1)):
286
+ if (self._SpotifyConnectZeroconfCastController.isPlaybackTransferError):
287
+ raise SpotifyConnectZeroconfPlaybackTransferError("%s - playback transfer error TODO" % (self.name))
288
+ break
289
+ if (counter >= timeout):
290
+ raise SpotifyConnectZeroconfPlaybackTransferError("%s - Timed out waiting for playback transfer to device" % (self.name))
291
+ counter += 1
292
+
293
+ # update task status.
294
+ _logsi.LogVerbose("%s - Transfer Playback complete for loginId \"%s\"" % (self.name, self.SpotifyClientInstance.SpotifyConnectLoginId))
295
+
296
+ # call the callback to process the transferSuccess or transferError.
297
+ self._CallZeroconfResponseReceivedCallback()
298
+
299
+ # event loop; we have to keep this thread active in order to keep the
300
+ # Spotify App on the Chromecast device active and available.
301
+ while True:
302
+
303
+ # keep going until we are asked to stop.
304
+ if (self.IsStopRequested):
305
+ _logsi.LogVerbose("%s - Thread task stop requested" % (self.name))
306
+ break
307
+ time.sleep(0.50)
308
+
309
+ # trace.
310
+ _logsi.LogVerbose("%s - Thread task was stopped" % (self.name))
311
+
312
+ except SpotifyConnectZeroconfPlaybackTransferError as ex:
313
+
314
+ # trace.
315
+ _logsi.LogError(str(ex))
316
+ _logsi.LogVerbose("%s - Thread task is ending due to error" % (self.name))
317
+
318
+ except Exception as ex:
319
+
320
+ # trace.
321
+ _logsi.LogException("%s - Exception: %s" % (self.name, str(ex)), ex, logToSystemLogger=False)
322
+ _logsi.LogVerbose("%s - Thread task is ending due to exception" % (self.name))
323
+
324
+ # raise unhandled exception.
325
+ raise
326
+
327
+ finally:
328
+
329
+ try:
330
+ # stop the cast app on the device.
331
+ # this will logout the current user from Spotify Connect Zeroconf as well.
332
+ if (self._SpotifyConnectZeroconfCastController is not None):
333
+ _logsi.LogVerbose("%s - Stopping Cast App" % (self.name))
334
+ self._SpotifyConnectZeroconfCastController.tear_down()
335
+ except:
336
+ pass