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.
- {spotifywebapiPython-1.0.147.dist-info → spotifywebapiPython-1.0.149.dist-info}/METADATA +5 -9
- {spotifywebapiPython-1.0.147.dist-info → spotifywebapiPython-1.0.149.dist-info}/RECORD +38 -29
- spotifywebapipython/__init__.py +0 -2
- spotifywebapipython/const.py +1 -1
- spotifywebapipython/docs/__init__.html +25 -4
- spotifywebapipython/docs/index.html +26 -2
- spotifywebapipython/docs/search.js +1 -1
- spotifywebapipython/docs/spotifywebapipython/const.html +3 -3
- spotifywebapipython/docs/spotifywebapipython/saappmessages.html +2 -2
- spotifywebapipython/docs/spotifywebapipython/sautils.html +8 -8
- spotifywebapipython/docs/spotifywebapipython/spotifyapierror.html +2 -2
- spotifywebapipython/docs/spotifywebapipython/spotifyapimessage.html +2 -2
- spotifywebapipython/docs/spotifywebapipython/spotifyauthtoken.html +2 -2
- spotifywebapipython/docs/spotifywebapipython/spotifyclient.html +514 -741
- spotifywebapipython/docs/spotifywebapipython/spotifydiscovery.html +3 -3
- spotifywebapipython/docs/spotifywebapipython/spotifywebapiauthenticationerror.html +2 -2
- spotifywebapipython/docs/spotifywebapipython/spotifywebapierror.html +2 -2
- spotifywebapipython/docs/spotifywebapipython/spotifywebplayertoken.html +466 -0
- spotifywebapipython/models/spotifyconnectdevice.py +84 -4
- spotifywebapipython/models/spotifyconnectdevices.py +16 -307
- spotifywebapipython/models/zeroconfdiscoveryresult.py +0 -41
- spotifywebapipython/sautils.py +0 -1
- spotifywebapipython/spotifyclient.py +1384 -1835
- spotifywebapipython/spotifyconnect/__init__.py +19 -0
- spotifywebapipython/{spotifyconnectdeviceeventargs.py → spotifyconnect/spotifyconnectdeviceeventargs.py} +1 -1
- spotifywebapipython/spotifyconnect/spotifyconnectdirectorytask.py +1967 -0
- spotifywebapipython/spotifyconnect/spotifyconnectzeroconfcastapptask.py +336 -0
- spotifywebapipython/spotifyconnect/spotifyconnectzeroconfcastcontroller.py +609 -0
- spotifywebapipython/spotifyconnect/spotifyconnectzeroconfcastlistener.py +316 -0
- spotifywebapipython/spotifyconnect/spotifyconnectzeroconfexceptions.py +10 -0
- spotifywebapipython/spotifyconnect/spotifyconnectzeroconflistener.py +335 -0
- spotifywebapipython/spotifydiscovery.py +0 -1
- spotifywebapipython/spotifywebplayertoken.py +344 -0
- spotifywebapipython/zeroconfapi/zeroconfgetinfo.py +4 -52
- spotifywebapipython/zeroconfapi/zeroconfresponse.py +8 -0
- {spotifywebapiPython-1.0.147.dist-info → spotifywebapiPython-1.0.149.dist-info}/LICENSE +0 -0
- {spotifywebapiPython-1.0.147.dist-info → spotifywebapiPython-1.0.149.dist-info}/WHEEL +0 -0
- {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
|