spotifywebapiPython 1.0.78__py3-none-any.whl → 1.0.80__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.78.dist-info → spotifywebapiPython-1.0.80.dist-info}/METADATA +1 -1
- {spotifywebapiPython-1.0.78.dist-info → spotifywebapiPython-1.0.80.dist-info}/RECORD +25 -25
- spotifywebapipython/const.py +6 -1
- spotifywebapipython/docs/__init__.html +18 -3
- spotifywebapipython/docs/index.html +2 -2
- spotifywebapipython/docs/search.js +1 -1
- spotifywebapipython/docs/spotifywebapiPython/const.html +20 -3
- spotifywebapipython/docs/spotifywebapiPython/saappmessages.html +2 -2
- spotifywebapipython/docs/spotifywebapiPython/sautils.html +2 -2
- spotifywebapipython/docs/spotifywebapiPython/spotifyapierror.html +2 -2
- spotifywebapipython/docs/spotifywebapiPython/spotifyapimessage.html +2 -2
- spotifywebapipython/docs/spotifywebapiPython/spotifyauthtoken.html +38 -2
- spotifywebapipython/docs/spotifywebapiPython/spotifyclient.html +168 -72
- spotifywebapipython/docs/spotifywebapiPython/spotifydiscovery.html +2 -2
- spotifywebapipython/docs/spotifywebapiPython/spotifywebapiauthenticationerror.html +2 -2
- spotifywebapipython/docs/spotifywebapiPython/spotifywebapierror.html +2 -2
- spotifywebapipython/models/spotifyconnectdevice.py +60 -1
- spotifywebapipython/models/spotifyconnectdevices.py +2 -0
- spotifywebapipython/spotifyauthtoken.py +26 -0
- spotifywebapipython/spotifyclient.py +440 -195
- spotifywebapipython/zeroconfapi/zeroconfconnect.py +52 -46
- spotifywebapipython/zeroconfapi/zeroconfgetinfoalias.py +8 -0
- {spotifywebapiPython-1.0.78.dist-info → spotifywebapiPython-1.0.80.dist-info}/LICENSE +0 -0
- {spotifywebapiPython-1.0.78.dist-info → spotifywebapiPython-1.0.80.dist-info}/WHEEL +0 -0
- {spotifywebapiPython-1.0.78.dist-info → spotifywebapiPython-1.0.80.dist-info}/top_level.txt +0 -0
|
@@ -119,7 +119,7 @@ class SpotifyClient:
|
|
|
119
119
|
The `spotifyConnectUsername`, `spotifyConnectPassword` and `spotifyConnectLoginId` arguments are only used
|
|
120
120
|
when a Spotify Connect account switch is performed on a selected player device. Note that these credentials
|
|
121
121
|
are NOT used to access the Spotify Web API (the Spotify Web API token is used for that).
|
|
122
|
-
Please see the `
|
|
122
|
+
Please see the `GetSpotifyConnectDevice` method for more details.
|
|
123
123
|
|
|
124
124
|
Note that if you don't have a password setup for your Spotify account (e.g. you utilize the "Continue with Google"
|
|
125
125
|
or other non-password methods for login), then you will need to define a "device password" in order to use the
|
|
@@ -6084,35 +6084,39 @@ class SpotifyClient:
|
|
|
6084
6084
|
# store the currently active user of the device, in case we need to switch users later on.
|
|
6085
6085
|
deviceActiveUser = info.ActiveUser.lower()
|
|
6086
6086
|
deviceIdResult = info.DeviceId
|
|
6087
|
-
|
|
6087
|
+
|
|
6088
6088
|
status:str = "INACTIVE, "
|
|
6089
6089
|
if (info.HasActiveUser):
|
|
6090
6090
|
status = "ACTIVE, "
|
|
6091
6091
|
|
|
6092
6092
|
# did we resolve the device id?
|
|
6093
6093
|
if deviceIdResult is not None:
|
|
6094
|
-
|
|
6095
|
-
#
|
|
6094
|
+
|
|
6095
|
+
# reset device reconnected flag.
|
|
6096
|
+
scDevice.WasReConnected = False
|
|
6097
|
+
|
|
6098
|
+
# is this a dynamic device? if so, then there is no need to activate it as it's already active.
|
|
6096
6099
|
if (discoverResult.IsDynamicDevice):
|
|
6097
|
-
_logsi.LogVerbose("Spotify Connect
|
|
6100
|
+
_logsi.LogVerbose("Activation not required for dynamic Spotify Connect device: '%s'" % (scDevice.Title))
|
|
6098
6101
|
status = status + "dynamic device, cannot switch user context"
|
|
6099
6102
|
|
|
6100
6103
|
# does our Spotify Connect user account need to take control of the device?
|
|
6101
6104
|
# if not, then we are done.
|
|
6102
|
-
# this takes into account if the device is in the
|
|
6105
|
+
# this takes into account if the device is in the available device list, as well
|
|
6103
6106
|
# as if the active user is not our user context.
|
|
6104
6107
|
elif (info.IsInDeviceList == True) \
|
|
6105
6108
|
and ((deviceActiveUser == self._SpotifyConnectUsername.lower()) or (deviceActiveUser == self.UserProfile.Id.lower())):
|
|
6106
|
-
_logsi.LogVerbose("
|
|
6109
|
+
_logsi.LogVerbose("User context '%s' was verified for Spotify Connect device '%s'; switch not necessary" % (deviceActiveUser, scDevice.Title))
|
|
6107
6110
|
status = status + "user context switch not needed"
|
|
6108
6111
|
|
|
6109
6112
|
# did caller request user context switch bypass?
|
|
6110
6113
|
# if so, then just return the device id.
|
|
6111
6114
|
elif (info.HasActiveUser) and (not verifyUserContext):
|
|
6112
|
-
_logsi.LogVerbose("
|
|
6115
|
+
_logsi.LogVerbose("User context '%s' will be used for Spotify Connect device '%s', as user chose to bypass user context switch" % (deviceActiveUser, scDevice.Title))
|
|
6113
6116
|
status = status + "caller bypassed user context switch"
|
|
6114
6117
|
|
|
6115
6118
|
else:
|
|
6119
|
+
|
|
6116
6120
|
zcfResult:ZeroconfResponse
|
|
6117
6121
|
|
|
6118
6122
|
# create a ZeroconfConnect object to access the device.
|
|
@@ -6128,18 +6132,21 @@ class SpotifyClient:
|
|
|
6128
6132
|
# if a different user context has control of the device then we need to disconnect
|
|
6129
6133
|
# the current user context before connecting a different user.
|
|
6130
6134
|
if (info.HasActiveUser):
|
|
6131
|
-
_logsi.LogVerbose("Spotify Connect
|
|
6135
|
+
_logsi.LogVerbose("Issuing Disconnect to Spotify Connect device '%s' for user context '%s'" % (scDevice.Title, deviceActiveUser))
|
|
6132
6136
|
zcfResult = zconn.Disconnect(delay)
|
|
6133
6137
|
status = status + 'disconnected from user context "%s";' % deviceActiveUser
|
|
6134
6138
|
|
|
6135
6139
|
# connect the device to OUR Spotify Connect user context.
|
|
6136
6140
|
# note that the result here only indicates that the connect was submitted - NOT that it was successful!
|
|
6137
|
-
_logsi.LogVerbose("Spotify Connect
|
|
6141
|
+
_logsi.LogVerbose("Issuing Connect to Spotify Connect device '%s' for user context '%s'" % (scDevice.Title, self._SpotifyConnectUsername))
|
|
6138
6142
|
zcfResult = zconn.Connect(self._SpotifyConnectUsername, self._SpotifyConnectPassword, self._SpotifyConnectLoginId, delay=delay)
|
|
6139
6143
|
status = status + 'connected to user context "%s"' % self._SpotifyConnectUsername
|
|
6144
|
+
|
|
6145
|
+
# indicate device was reconnected.
|
|
6146
|
+
scDevice.WasReConnected = True
|
|
6140
6147
|
|
|
6141
6148
|
# trace.
|
|
6142
|
-
_logsi.LogVerbose("
|
|
6149
|
+
_logsi.LogVerbose("User context switch from '%s' to '%s' was requested for Spotify Connect device '%s'" % (deviceActiveUser, self._SpotifyConnectUsername, scDevice.Title))
|
|
6143
6150
|
|
|
6144
6151
|
# append device status to results.
|
|
6145
6152
|
result = "%s%s\n" % (result, MSG_RESULT % (info.RemoteName, info.DeviceId, info.ActiveUser, status))
|
|
@@ -6538,6 +6545,18 @@ class SpotifyClient:
|
|
|
6538
6545
|
|
|
6539
6546
|
# process results.
|
|
6540
6547
|
result = PlayerPlayState(root=msg.ResponseData)
|
|
6548
|
+
|
|
6549
|
+
# check for a restricted device; if found, then grab the device id from the
|
|
6550
|
+
# cached list of spotify connect devices (if present).
|
|
6551
|
+
if result is not None:
|
|
6552
|
+
device:Device = result.Device
|
|
6553
|
+
if (device is not None) and (device.Id is None or device.Id == ''):
|
|
6554
|
+
cacheKey:str = 'GetSpotifyConnectDevices'
|
|
6555
|
+
if (cacheKey in self._ConfigurationCache):
|
|
6556
|
+
devices:SpotifyConnectDevices = self._ConfigurationCache[cacheKey]
|
|
6557
|
+
scDevice:SpotifyConnectDevice = devices.GetDeviceByName(device.Name)
|
|
6558
|
+
if scDevice is not None:
|
|
6559
|
+
device.Id = scDevice.DeviceInfo.DeviceId
|
|
6541
6560
|
|
|
6542
6561
|
# trace.
|
|
6543
6562
|
_logsi.LogObject(SILevel.Verbose, TRACE_METHOD_RESULT_TYPE % (apiMethodName, type(result).__name__), result, excludeNonPublic=True)
|
|
@@ -7984,9 +8003,299 @@ class SpotifyClient:
|
|
|
7984
8003
|
_logsi.LeaveMethod(SILevel.Debug, apiMethodName)
|
|
7985
8004
|
|
|
7986
8005
|
|
|
8006
|
+
def GetSpotifyConnectDevice(
|
|
8007
|
+
self,
|
|
8008
|
+
deviceValue:str,
|
|
8009
|
+
verifyUserContext:bool=True,
|
|
8010
|
+
verifyTimeout:float=5.0,
|
|
8011
|
+
refreshDeviceList:bool=False,
|
|
8012
|
+
activateDevice:bool=True,
|
|
8013
|
+
delay:float=0.25
|
|
8014
|
+
) -> Device:
|
|
8015
|
+
"""
|
|
8016
|
+
Get information about a specific Spotify Connect player device, and (optionally)
|
|
8017
|
+
activate the device if it requires it.
|
|
8018
|
+
|
|
8019
|
+
This method requires the `user-read-playback-state` scope.
|
|
8020
|
+
|
|
8021
|
+
Resolves a Spotify Connect device from a specified device id, name, alias id,
|
|
8022
|
+
or alias name. This will ensure that the device can be found on the network, as well
|
|
8023
|
+
as connect to the device if necessary with the current user context.
|
|
8024
|
+
|
|
8025
|
+
Args:
|
|
8026
|
+
deviceValue (str):
|
|
8027
|
+
The device id / name value to check.
|
|
8028
|
+
verifyUserContext (bool):
|
|
8029
|
+
If True, the active user context of the resolved device is checked to ensure it
|
|
8030
|
+
matches the user context specified on the class constructor.
|
|
8031
|
+
If False, the user context will not be checked.
|
|
8032
|
+
Default is True.
|
|
8033
|
+
verifyTimeout (float):
|
|
8034
|
+
Maximum time to wait (in seconds) for the device to become active in the Spotify
|
|
8035
|
+
Connect device list. This value is only used if a Connect command has to be
|
|
8036
|
+
issued to activate the device.
|
|
8037
|
+
Default is 5; value range is 0 - 10.
|
|
8038
|
+
refreshDeviceList (bool):
|
|
8039
|
+
True to refresh the Spotify Connect device list; otherwise, False to use the
|
|
8040
|
+
Spotify Connect device list cache.
|
|
8041
|
+
activateDevice (bool):
|
|
8042
|
+
True to activate the device if necessary; otherwise, False.
|
|
8043
|
+
delay (float):
|
|
8044
|
+
Time delay (in seconds) to wait AFTER issuing any command to the device.
|
|
8045
|
+
This delay will give the spotify zeroconf api time to process the change before
|
|
8046
|
+
another command is issued.
|
|
8047
|
+
Default is 0.25; value range is 0 - 10.
|
|
8048
|
+
|
|
8049
|
+
Returns:
|
|
8050
|
+
A `Device` object for the deviceValue if one could be resolved;
|
|
8051
|
+
otherwise, a null value with the understanding that subsequent operations will probably
|
|
8052
|
+
fail since it's not in the Spotify Connect discovery list.
|
|
8053
|
+
|
|
8054
|
+
Raises:
|
|
8055
|
+
SpotifyWebApiError:
|
|
8056
|
+
If the Spotify Web API request was for a non-authorization service
|
|
8057
|
+
and the response contains error information.
|
|
8058
|
+
SpotifyZeroconfApiError:
|
|
8059
|
+
If any Spotify Zeroconf API request failed.
|
|
8060
|
+
SpotifApiError:
|
|
8061
|
+
If the method fails for any other reason.
|
|
8062
|
+
|
|
8063
|
+
The Spotify Connect discovery list is searched for the specified device value; the search will
|
|
8064
|
+
match on either a Device ID or RemoteName as well as any alias ID's or Names that are in use.
|
|
8065
|
+
The cached Spotify Connect discovery list will be used if the `refreshDeviceList` argument is
|
|
8066
|
+
False, or if the cache is empty; otherwise, Spotify Connect discovery list will be refreshed
|
|
8067
|
+
and the cache updated prior to the search.
|
|
8068
|
+
|
|
8069
|
+
The `SpotifyConnectDevice` object is returned if a match is found; otherwise, a null value is
|
|
8070
|
+
returned.
|
|
8071
|
+
|
|
8072
|
+
A user context switch will also be performed if a device id is resolved AND the active user of the
|
|
8073
|
+
device does not match the `SpotifyConnectUsername` argument specified on the class constructor.
|
|
8074
|
+
The user context switch is bypassed if the `verifyUserContext` argument is False, or if the
|
|
8075
|
+
`SpotifyConnectUsername` was not supplied on the class constructor. If the user context is to be
|
|
8076
|
+
switched, then a Disconnect will be issued if a user context is active on the device followed by
|
|
8077
|
+
a Connect to the user context specified on the class constructor.
|
|
8078
|
+
|
|
8079
|
+
<details>
|
|
8080
|
+
<summary>Sample Code</summary>
|
|
8081
|
+
```python
|
|
8082
|
+
.. include:: ../docs/include/samplecode/SpotifyClient/GetSpotifyConnectDevice.py
|
|
8083
|
+
```
|
|
8084
|
+
</details>
|
|
8085
|
+
"""
|
|
8086
|
+
apiMethodName:str = 'GetSpotifyConnectDevice'
|
|
8087
|
+
apiMethodParms:SIMethodParmListContext = None
|
|
8088
|
+
deviceResultId:str = None
|
|
8089
|
+
deviceResult:SpotifyConnectDevice = None
|
|
8090
|
+
|
|
8091
|
+
try:
|
|
8092
|
+
|
|
8093
|
+
# trace.
|
|
8094
|
+
apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
|
|
8095
|
+
apiMethodParms.AppendKeyValue("deviceValue", deviceValue)
|
|
8096
|
+
apiMethodParms.AppendKeyValue("verifyUserContext", verifyUserContext)
|
|
8097
|
+
apiMethodParms.AppendKeyValue("verifyTimeout", verifyTimeout)
|
|
8098
|
+
apiMethodParms.AppendKeyValue("refreshDeviceList", refreshDeviceList)
|
|
8099
|
+
apiMethodParms.AppendKeyValue("activateDevice", activateDevice)
|
|
8100
|
+
apiMethodParms.AppendKeyValue("delay", delay)
|
|
8101
|
+
_logsi.LogMethodParmList(SILevel.Verbose, "Resolving Spotify Connect Device", apiMethodParms)
|
|
8102
|
+
|
|
8103
|
+
# if device value not specified then we are done.
|
|
8104
|
+
if (deviceValue is None) or (len(deviceValue.strip()) == 0):
|
|
8105
|
+
_logsi.LogVerbose("deviceValue not specified; active Spotify Player will be used")
|
|
8106
|
+
return None
|
|
8107
|
+
|
|
8108
|
+
# validations.
|
|
8109
|
+
delay = validateDelay(delay, 0.25, 10)
|
|
8110
|
+
verifyTimeout = validateDelay(verifyTimeout, 5, 10)
|
|
8111
|
+
if (not isinstance(verifyUserContext, bool)):
|
|
8112
|
+
verifyUserContext = True
|
|
8113
|
+
|
|
8114
|
+
# determine if the device value specified is a name (e.g. "Bose-ST10-1") or
|
|
8115
|
+
# an id (e.g. "30fbc80e35598f3c242f2120413c943dfd9715fe", "48b677ca-ef9b-516f-b702-93bf2e8c67ba").
|
|
8116
|
+
isDeviceId:bool = SpotifyClient.IsDeviceId(deviceValue)
|
|
8117
|
+
|
|
8118
|
+
deviceValueCompare:str = deviceValue.lower()
|
|
8119
|
+
deviceActiveUser:str = None
|
|
8120
|
+
discoverResult:ZeroconfDiscoveryResult
|
|
8121
|
+
info:ZeroconfGetInfo
|
|
8122
|
+
scDevice:SpotifyConnectDevice
|
|
8123
|
+
|
|
8124
|
+
# get all available Spotify Connect devices on the network.
|
|
8125
|
+
scDevices:SpotifyConnectDevices = self.GetSpotifyConnectDevices(refresh=refreshDeviceList)
|
|
8126
|
+
|
|
8127
|
+
# process all discovered devices.
|
|
8128
|
+
for scDevice in scDevices:
|
|
8129
|
+
|
|
8130
|
+
discoverResult = scDevice.DiscoveryResult
|
|
8131
|
+
info = scDevice.DeviceInfo
|
|
8132
|
+
|
|
8133
|
+
# store the currently active user of the device, in case we need to switch users later on.
|
|
8134
|
+
deviceActiveUser = info.ActiveUser.lower()
|
|
8135
|
+
|
|
8136
|
+
# are aliases being used (RemoteName is null if so)?
|
|
8137
|
+
if info.HasAliases:
|
|
8138
|
+
|
|
8139
|
+
# if aliases are defined for the device, then we have to check each alias for a match.
|
|
8140
|
+
infoAlias:ZeroconfGetInfoAlias
|
|
8141
|
+
for infoAlias in info.Aliases:
|
|
8142
|
+
|
|
8143
|
+
# if input deviceValue is a device id then compare the id; otherwise, compare the name.
|
|
8144
|
+
if isDeviceId:
|
|
8145
|
+
if (infoAlias.Id.lower() == deviceValueCompare):
|
|
8146
|
+
_logsi.LogVerbose("Device value '%s' was matched to device Alias: %s" % (deviceValue, infoAlias.Title))
|
|
8147
|
+
deviceResultId = infoAlias.Id
|
|
8148
|
+
deviceResult = scDevice
|
|
8149
|
+
break # alias processing loop
|
|
8150
|
+
else:
|
|
8151
|
+
if (infoAlias.Name.lower() == deviceValueCompare):
|
|
8152
|
+
_logsi.LogVerbose("Device value '%s' was matched to device Alias: %s" % (deviceValue, infoAlias.Title))
|
|
8153
|
+
deviceResultId = infoAlias.Id
|
|
8154
|
+
deviceResult = scDevice
|
|
8155
|
+
break # alias processing loop
|
|
8156
|
+
|
|
8157
|
+
else:
|
|
8158
|
+
|
|
8159
|
+
# if input deviceValue is a device id then compare the id; otherwise, compare the name.
|
|
8160
|
+
if isDeviceId:
|
|
8161
|
+
if (scDevice.Id.lower() == deviceValueCompare):
|
|
8162
|
+
_logsi.LogVerbose("Device value '%s' was matched to device: '%s'" % (deviceValue, scDevice.Title))
|
|
8163
|
+
deviceResultId = scDevice.Id
|
|
8164
|
+
deviceResult = scDevice
|
|
8165
|
+
else:
|
|
8166
|
+
if (scDevice.Name.lower() == deviceValueCompare):
|
|
8167
|
+
_logsi.LogVerbose("Device value '%s' was matched to device: '%s'" % (deviceValue, scDevice.Title))
|
|
8168
|
+
deviceResultId = scDevice.Id
|
|
8169
|
+
deviceResult = scDevice
|
|
8170
|
+
|
|
8171
|
+
# did we resolve the device id?
|
|
8172
|
+
if deviceResult is not None:
|
|
8173
|
+
|
|
8174
|
+
# at this point, we will try to activate the device if it requires it.
|
|
8175
|
+
|
|
8176
|
+
# reset device reconnected flag.
|
|
8177
|
+
scDevice.WasReConnected = False
|
|
8178
|
+
|
|
8179
|
+
# was device activation requested? if not, then we are done.
|
|
8180
|
+
if activateDevice == False:
|
|
8181
|
+
_logsi.LogVerbose("Activation not requested for Spotify Connect device: '%s'" % (deviceResult.Title))
|
|
8182
|
+
break
|
|
8183
|
+
|
|
8184
|
+
# is this a dynamic device? if so, then there is no need to activate it as it's already active.
|
|
8185
|
+
if (discoverResult.IsDynamicDevice):
|
|
8186
|
+
_logsi.LogVerbose("Activation not required for dynamic Spotify Connect device: '%s'" % (deviceResult.Title))
|
|
8187
|
+
break
|
|
8188
|
+
|
|
8189
|
+
# did caller request user context switch bypass?
|
|
8190
|
+
# if so, then just return the device.
|
|
8191
|
+
if (not verifyUserContext):
|
|
8192
|
+
_logsi.LogVerbose("User context '%s' will be used for Spotify Connect device '%s', as user chose to bypass user context switch" % (deviceActiveUser, deviceResult.Title))
|
|
8193
|
+
break
|
|
8194
|
+
|
|
8195
|
+
# was a Spotify Connect user account specified on the class constructor?
|
|
8196
|
+
# if not, then do not switch the user context.
|
|
8197
|
+
if (self._SpotifyConnectUsername is None):
|
|
8198
|
+
_logsi.LogVerbose("User context '%s' will be used for Spotify Connect device '%s', as a SpotifyConnectUsername parameter was not supplied" % (deviceActiveUser, deviceResult.Title))
|
|
8199
|
+
break
|
|
8200
|
+
|
|
8201
|
+
# does our Spotify Connect user account need to take control of the device?
|
|
8202
|
+
# if not, then we are done.
|
|
8203
|
+
# this takes into account if the device is in the available device list, as well
|
|
8204
|
+
# as if the active user is not our user context.
|
|
8205
|
+
if (info.IsInDeviceList == True) \
|
|
8206
|
+
and ((deviceActiveUser == self._SpotifyConnectUsername.lower()) or (deviceActiveUser == self.UserProfile.Id.lower())):
|
|
8207
|
+
_logsi.LogVerbose("User context '%s' was verified for Spotify Connect device '%s'; switch not necessary" % (deviceActiveUser, deviceResult.Title))
|
|
8208
|
+
break
|
|
8209
|
+
|
|
8210
|
+
zcfResult:ZeroconfResponse
|
|
8211
|
+
|
|
8212
|
+
# create a ZeroconfConnect object to access the device.
|
|
8213
|
+
# note that we are using the "HostIpAddress" property value here, with "Server" as a fallback.
|
|
8214
|
+
# the "Server" property is an alias, which must be resolved via a DNS lookup under
|
|
8215
|
+
# the covers and adds a significant delay (2-3 seconds!) to the activation time.
|
|
8216
|
+
zconn:ZeroconfConnect = ZeroconfConnect(discoverResult.HostIpAddress,
|
|
8217
|
+
discoverResult.HostIpPort,
|
|
8218
|
+
discoverResult.SpotifyConnectCPath,
|
|
8219
|
+
useSSL=False,
|
|
8220
|
+
tokenStorageDir=self.TokenStorageDir)
|
|
8221
|
+
|
|
8222
|
+
# if a different user context has control of the device then we need to disconnect
|
|
8223
|
+
# the current user context before connecting a different user.
|
|
8224
|
+
if (info.HasActiveUser):
|
|
8225
|
+
_logsi.LogVerbose("Issuing Disconnect to Spotify Connect device '%s' for user context '%s'" % (deviceResult.Title, deviceActiveUser))
|
|
8226
|
+
zcfResult = zconn.Disconnect(delay)
|
|
8227
|
+
|
|
8228
|
+
# connect the device to OUR Spotify Connect user context.
|
|
8229
|
+
# note that the result here only indicates that the connect was submitted - NOT that it was successful!
|
|
8230
|
+
_logsi.LogVerbose("Issuing Connect to Spotify Connect device '%s' for user context '%s'" % (deviceResult.Title, self._SpotifyConnectUsername))
|
|
8231
|
+
zcfResult = zconn.Connect(self._SpotifyConnectUsername, self._SpotifyConnectPassword, self._SpotifyConnectLoginId)
|
|
8232
|
+
|
|
8233
|
+
# indicate device was reconnected.
|
|
8234
|
+
deviceResult.WasReConnected = True
|
|
8235
|
+
|
|
8236
|
+
# trace.
|
|
8237
|
+
_logsi.LogVerbose("User context switch from '%s' to '%s' was requested for Spotify Connect device: '%s'" % (deviceActiveUser, self._SpotifyConnectUsername, deviceResult.Title))
|
|
8238
|
+
|
|
8239
|
+
loopTotalDelay:float = 0
|
|
8240
|
+
LOOP_DELAY:float = 0.25
|
|
8241
|
+
while True:
|
|
8242
|
+
|
|
8243
|
+
# wait just a bit between available device list queries.
|
|
8244
|
+
_logsi.LogVerbose(TRACE_MSG_DELAY_DEVICE % LOOP_DELAY)
|
|
8245
|
+
time.sleep(LOOP_DELAY)
|
|
8246
|
+
loopTotalDelay = loopTotalDelay + LOOP_DELAY
|
|
8247
|
+
|
|
8248
|
+
# check for the device in the available device list.
|
|
8249
|
+
# if we find it, then we are done.
|
|
8250
|
+
device:Device = self.GetPlayerDevice(deviceResultId, True)
|
|
8251
|
+
if device is not None:
|
|
8252
|
+
_logsi.LogVerbose("Spotify Connect device '%s' is now in the available device list; device found within %f seconds of Connect" % (deviceResult.Title, loopTotalDelay))
|
|
8253
|
+
break
|
|
8254
|
+
|
|
8255
|
+
# check if the active device is restricted; if it is, then it may not show up in the
|
|
8256
|
+
# available device list of devices (common issue with Sonos devices). in this case,
|
|
8257
|
+
# it WILL show up in the player state device property as the active device.
|
|
8258
|
+
# note that the PlayerPlayState device is a name and not a device id.
|
|
8259
|
+
playerState:PlayerPlayState = self.GetPlayerPlaybackState()
|
|
8260
|
+
if playerState.Device is not None:
|
|
8261
|
+
device = playerState.Device
|
|
8262
|
+
if (device.Id == deviceResultId) or (device.Name == deviceResultId):
|
|
8263
|
+
_logsi.LogVerbose("Spotify Connect device '%s' is now the active device; device found within %f seconds of Connect" % (deviceResult.Title, loopTotalDelay))
|
|
8264
|
+
break
|
|
8265
|
+
|
|
8266
|
+
# only check so many times before we give up;
|
|
8267
|
+
# we will keep checking until we find the device, or we exceed the verifyTimeout value.
|
|
8268
|
+
if (loopTotalDelay > verifyTimeout):
|
|
8269
|
+
_logsi.LogWarning("Verification timeout waiting for Spotify Connect device '%s' to be added to the available device list; device not found within %f seconds of Connect" % (deviceResult.Title, verifyTimeout))
|
|
8270
|
+
break
|
|
8271
|
+
|
|
8272
|
+
# end the device processing loop.
|
|
8273
|
+
break
|
|
8274
|
+
|
|
8275
|
+
# did we resolve the device id?
|
|
8276
|
+
if deviceResult is None:
|
|
8277
|
+
_logsi.LogWarning("Spotify Connect device '%s' could not be resolved; please ensure that the specified device is powered on and available on the local network" % (deviceValue))
|
|
8278
|
+
|
|
8279
|
+
# return the resolved device id.
|
|
8280
|
+
return deviceResult
|
|
8281
|
+
|
|
8282
|
+
except SpotifyZeroconfApiError: raise # pass handled exceptions on thru
|
|
8283
|
+
except SpotifyApiError: raise # pass handled exceptions on thru
|
|
8284
|
+
except Exception as ex:
|
|
8285
|
+
|
|
8286
|
+
# format unhandled exception.
|
|
8287
|
+
raise SpotifyApiError(SAAppMessages.UNHANDLED_EXCEPTION.format(apiMethodName, str(ex)), ex, logsi=_logsi)
|
|
8288
|
+
|
|
8289
|
+
finally:
|
|
8290
|
+
|
|
8291
|
+
# trace.
|
|
8292
|
+
_logsi.LeaveMethod(SILevel.Debug, apiMethodName)
|
|
8293
|
+
|
|
8294
|
+
|
|
7987
8295
|
def GetSpotifyConnectDevices(
|
|
7988
8296
|
self,
|
|
7989
|
-
refresh:bool=True
|
|
8297
|
+
refresh:bool=True,
|
|
8298
|
+
sortResult:bool=True,
|
|
7990
8299
|
) -> SpotifyConnectDevices:
|
|
7991
8300
|
"""
|
|
7992
8301
|
Get information about all available Spotify Connect player devices.
|
|
@@ -7997,6 +8306,10 @@ class SpotifyClient:
|
|
|
7997
8306
|
refresh (bool):
|
|
7998
8307
|
True (default) to return real-time information from the spotify zeroconf api and
|
|
7999
8308
|
update the cache; otherwise, False to just return the cached value.
|
|
8309
|
+
sortResult (bool):
|
|
8310
|
+
True to sort the items by name; otherwise, False to leave the items in the same order they
|
|
8311
|
+
were returned in by the Spotify Web API.
|
|
8312
|
+
Default: True
|
|
8000
8313
|
|
|
8001
8314
|
Returns:
|
|
8002
8315
|
A `SpotifyConnectDevices` object that contain the discovery results
|
|
@@ -8038,20 +8351,24 @@ class SpotifyClient:
|
|
|
8038
8351
|
</details>
|
|
8039
8352
|
"""
|
|
8040
8353
|
apiMethodName:str = 'GetSpotifyConnectDevices'
|
|
8354
|
+
apiMethodParms:SIMethodParmListContext = None
|
|
8041
8355
|
result:SpotifyConnectDevices = SpotifyConnectDevices()
|
|
8042
8356
|
cacheDesc:str = CACHE_SOURCE_CURRENT
|
|
8043
8357
|
|
|
8044
8358
|
try:
|
|
8045
8359
|
|
|
8046
8360
|
# trace.
|
|
8047
|
-
_logsi.
|
|
8361
|
+
apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
|
|
8362
|
+
apiMethodParms.AppendKeyValue("refresh", refresh)
|
|
8363
|
+
apiMethodParms.AppendKeyValue("sortResult", sortResult)
|
|
8364
|
+
_logsi.LogMethodParmList(SILevel.Verbose, "Get available Spotify Connect devices", apiMethodParms)
|
|
8048
8365
|
|
|
8049
8366
|
# can we use the cached value?
|
|
8050
8367
|
if (not refresh) and (apiMethodName in self._ConfigurationCache):
|
|
8051
8368
|
|
|
8052
8369
|
result = self._ConfigurationCache[apiMethodName]
|
|
8053
8370
|
cacheDesc = CACHE_SOURCE_CACHED
|
|
8054
|
-
|
|
8371
|
+
|
|
8055
8372
|
else:
|
|
8056
8373
|
|
|
8057
8374
|
# discover Spotify Connect devices on the network, waiting up to the specified timeout
|
|
@@ -8134,6 +8451,8 @@ class SpotifyClient:
|
|
|
8134
8451
|
|
|
8135
8452
|
# create new spotify connect device object.
|
|
8136
8453
|
scDevice:SpotifyConnectDevice = SpotifyConnectDevice()
|
|
8454
|
+
scDevice.Id = info.DeviceId
|
|
8455
|
+
scDevice.Name = info.RemoteName
|
|
8137
8456
|
scDevice.DiscoveryResult = discoverResult
|
|
8138
8457
|
scDevice.DeviceInfo = info
|
|
8139
8458
|
result.Items.append(scDevice)
|
|
@@ -8151,9 +8470,10 @@ class SpotifyClient:
|
|
|
8151
8470
|
|
|
8152
8471
|
# is this a dynamic device?
|
|
8153
8472
|
if not result.ContainsDeviceId(device.Id):
|
|
8473
|
+
_logsi.LogVerbose("Spotify Connect dynamic device detected: '%s' (%s)" % (device.Name, device.Id))
|
|
8154
8474
|
result.AddDynamicDevice(device, self.UserProfile.Id)
|
|
8155
8475
|
|
|
8156
|
-
# is the device in the
|
|
8476
|
+
# is the device in the available device list for this user context?
|
|
8157
8477
|
# if not, then set an indicator so we can re-activate it later if need be.
|
|
8158
8478
|
scDevice:SpotifyConnectDevice
|
|
8159
8479
|
for scDevice in result:
|
|
@@ -8161,10 +8481,30 @@ class SpotifyClient:
|
|
|
8161
8481
|
scDevice.DeviceInfo.IsInDeviceList = True
|
|
8162
8482
|
scDevice.DeviceInfo.IsActiveDevice = device.IsActive
|
|
8163
8483
|
break
|
|
8484
|
+
|
|
8485
|
+
# get the currently active device (if any).
|
|
8486
|
+
# we do this in case the active device is a "restricted" device; if it is, then it may
|
|
8487
|
+
# not show up as the active device (common issue with Sonos devices).
|
|
8488
|
+
# in this case, it WILL show up in the player state device property as the active device.
|
|
8489
|
+
# if it IS active, then we will set the active flag.
|
|
8490
|
+
# note that the PlayerPlayState device is a name and not a device id.
|
|
8491
|
+
playerState:PlayerPlayState = self.GetPlayerPlaybackState()
|
|
8492
|
+
if playerState.Device is not None:
|
|
8493
|
+
device = playerState.Device
|
|
8494
|
+
scDevice:SpotifyConnectDevice
|
|
8495
|
+
for scDevice in result:
|
|
8496
|
+
if (device.Name == scDevice.Name):
|
|
8497
|
+
_logsi.LogVerbose("Spotify Connect active device detected: '%s' (%s)" % (device.Name, device.Id))
|
|
8498
|
+
scDevice.DeviceInfo.IsActiveDevice = True
|
|
8499
|
+
break
|
|
8164
8500
|
|
|
8165
8501
|
# update cache.
|
|
8166
8502
|
self._ConfigurationCache[apiMethodName] = result
|
|
8167
8503
|
|
|
8504
|
+
# sort items on Name property, ascending order.
|
|
8505
|
+
if (len(result.Items) > 0) and (sortResult is True):
|
|
8506
|
+
result.Items.sort(key=lambda x: (x.Name or "").lower(), reverse=False)
|
|
8507
|
+
|
|
8168
8508
|
# update last refresh date.
|
|
8169
8509
|
result.DateLastRefreshed = datetime.utcnow().timestamp()
|
|
8170
8510
|
|
|
@@ -9728,7 +10068,8 @@ class SpotifyClient:
|
|
|
9728
10068
|
Default is 0.50; value range is 0 - 10.
|
|
9729
10069
|
resolveDeviceId (bool):
|
|
9730
10070
|
True to resolve the supplied `deviceId` value; otherwise, False not resolve the `deviceId`
|
|
9731
|
-
value as it has already been resolved.
|
|
10071
|
+
value as it has already been resolved.
|
|
10072
|
+
Default is True.
|
|
9732
10073
|
|
|
9733
10074
|
Raises:
|
|
9734
10075
|
SpotifyWebApiError:
|
|
@@ -9786,8 +10127,17 @@ class SpotifyClient:
|
|
|
9786
10127
|
|
|
9787
10128
|
# ensure the specified device id / name is active and available, and
|
|
9788
10129
|
# the user context is set correctly.
|
|
9789
|
-
|
|
9790
|
-
|
|
10130
|
+
scDevice = self.GetSpotifyConnectDevice(
|
|
10131
|
+
deviceId,
|
|
10132
|
+
refreshDeviceList=False,
|
|
10133
|
+
activateDevice=True,
|
|
10134
|
+
verifyUserContext=True)
|
|
10135
|
+
|
|
10136
|
+
# did we find the device?
|
|
10137
|
+
# if so, then use the found device id in case a device name was specified.
|
|
10138
|
+
if scDevice is not None:
|
|
10139
|
+
deviceId = scDevice.Id
|
|
10140
|
+
|
|
9791
10141
|
# build spotify web api request parameters.
|
|
9792
10142
|
reqData:dict = \
|
|
9793
10143
|
{
|
|
@@ -9860,7 +10210,8 @@ class SpotifyClient:
|
|
|
9860
10210
|
Default is 0.50; value range is 0 - 10.
|
|
9861
10211
|
resolveDeviceId (bool):
|
|
9862
10212
|
True to resolve the supplied `deviceId` value; otherwise, False not resolve the `deviceId`
|
|
9863
|
-
value as it has already been resolved.
|
|
10213
|
+
value as it has already been resolved.
|
|
10214
|
+
Default is True.
|
|
9864
10215
|
limitTotal (int):
|
|
9865
10216
|
The maximum number of items to retrieve from favorites.
|
|
9866
10217
|
Default: 200
|
|
@@ -9918,7 +10269,16 @@ class SpotifyClient:
|
|
|
9918
10269
|
|
|
9919
10270
|
# ensure the specified device id / name is active and available, and
|
|
9920
10271
|
# the user context is set correctly.
|
|
9921
|
-
|
|
10272
|
+
scDevice = self.GetSpotifyConnectDevice(
|
|
10273
|
+
deviceId,
|
|
10274
|
+
refreshDeviceList=False,
|
|
10275
|
+
activateDevice=True,
|
|
10276
|
+
verifyUserContext=True)
|
|
10277
|
+
|
|
10278
|
+
# did we find the device?
|
|
10279
|
+
# if so, then use the found device id in case a device name was specified.
|
|
10280
|
+
if scDevice is not None:
|
|
10281
|
+
deviceId = scDevice.Id
|
|
9922
10282
|
|
|
9923
10283
|
# set desired shuffle mode.
|
|
9924
10284
|
if shuffle is not None:
|
|
@@ -9984,7 +10344,8 @@ class SpotifyClient:
|
|
|
9984
10344
|
Default is 0.50; value range is 0 - 10.
|
|
9985
10345
|
resolveDeviceId (bool):
|
|
9986
10346
|
True to resolve the supplied `deviceId` value; otherwise, False not resolve the `deviceId`
|
|
9987
|
-
value as it has already been resolved.
|
|
10347
|
+
value as it has already been resolved.
|
|
10348
|
+
Default is True.
|
|
9988
10349
|
|
|
9989
10350
|
Raises:
|
|
9990
10351
|
SpotifyWebApiError:
|
|
@@ -10036,7 +10397,16 @@ class SpotifyClient:
|
|
|
10036
10397
|
|
|
10037
10398
|
# ensure the specified device id / name is active and available, and
|
|
10038
10399
|
# the user context is set correctly.
|
|
10039
|
-
|
|
10400
|
+
scDevice = self.GetSpotifyConnectDevice(
|
|
10401
|
+
deviceId,
|
|
10402
|
+
refreshDeviceList=False,
|
|
10403
|
+
activateDevice=True,
|
|
10404
|
+
verifyUserContext=True)
|
|
10405
|
+
|
|
10406
|
+
# did we find the device?
|
|
10407
|
+
# if so, then use the found device id in case a device name was specified.
|
|
10408
|
+
if scDevice is not None:
|
|
10409
|
+
deviceId = scDevice.Id
|
|
10040
10410
|
|
|
10041
10411
|
# build spotify web api request parameters.
|
|
10042
10412
|
reqData:dict = \
|
|
@@ -10455,6 +10825,9 @@ class SpotifyClient:
|
|
|
10455
10825
|
delay:float=0.25
|
|
10456
10826
|
) -> str:
|
|
10457
10827
|
"""
|
|
10828
|
+
This method has been deprecated.
|
|
10829
|
+
Use the `GetSpotifyConnectDevice` method instead.
|
|
10830
|
+
|
|
10458
10831
|
Resolves a Spotify Connect device identifier from a specified device id, name, alias id,
|
|
10459
10832
|
or alias name. This will ensure that the device id can be found on the network, as well
|
|
10460
10833
|
as connect to the device if necessary with the current user context.
|
|
@@ -10508,13 +10881,6 @@ class SpotifyClient:
|
|
|
10508
10881
|
`SpotifyConnectUsername` was not supplied on the class constructor. If the user context is to be
|
|
10509
10882
|
switched, then a Disconnect will be issued if a user context is active on the device followed by
|
|
10510
10883
|
a Connect to the user context specified on the class constructor.
|
|
10511
|
-
|
|
10512
|
-
<details>
|
|
10513
|
-
<summary>Sample Code</summary>
|
|
10514
|
-
```python
|
|
10515
|
-
.. include:: ../docs/include/samplecode/SpotifyClient/PlayerResolveDeviceId.py
|
|
10516
|
-
```
|
|
10517
|
-
</details>
|
|
10518
10884
|
"""
|
|
10519
10885
|
apiMethodName:str = 'PlayerResolveDeviceId'
|
|
10520
10886
|
apiMethodParms:SIMethodParmListContext = None
|
|
@@ -10522,6 +10888,9 @@ class SpotifyClient:
|
|
|
10522
10888
|
|
|
10523
10889
|
try:
|
|
10524
10890
|
|
|
10891
|
+
# TODO method is deprecated as of 2024/08/15, v1.0.80.
|
|
10892
|
+
# remove in a future release.
|
|
10893
|
+
|
|
10525
10894
|
# trace.
|
|
10526
10895
|
apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
|
|
10527
10896
|
apiMethodParms.AppendKeyValue("deviceValue", deviceValue)
|
|
@@ -10530,163 +10899,24 @@ class SpotifyClient:
|
|
|
10530
10899
|
apiMethodParms.AppendKeyValue("delay", delay)
|
|
10531
10900
|
_logsi.LogMethodParmList(SILevel.Verbose, "Resolving Spotify Connect Player Device Id", apiMethodParms)
|
|
10532
10901
|
|
|
10533
|
-
#
|
|
10534
|
-
|
|
10535
|
-
|
|
10902
|
+
# trace.
|
|
10903
|
+
_logsi.LogWarning("SpotifyWebApiPython `PlayerResolveDeviceId` method is deprecated, and will be removed in a future release; use the `GetSpotifyConnectDevice` instead.")
|
|
10904
|
+
|
|
10905
|
+
# call newer method to resolve the device id.
|
|
10906
|
+
scDevice:SpotifyConnectDevice = self.GetSpotifyConnectDevice(
|
|
10907
|
+
deviceValue=deviceValue,
|
|
10908
|
+
verifyUserContext=verifyUserContext,
|
|
10909
|
+
verifyTimeout=verifyTimeout,
|
|
10910
|
+
refreshDeviceList=True,
|
|
10911
|
+
activateDevice=True,
|
|
10912
|
+
delay=delay)
|
|
10913
|
+
|
|
10914
|
+
# if device not found then we are done.
|
|
10915
|
+
if scDevice is None:
|
|
10536
10916
|
return None
|
|
10537
10917
|
|
|
10538
|
-
#
|
|
10539
|
-
|
|
10540
|
-
verifyTimeout = validateDelay(verifyTimeout, 5, 10)
|
|
10541
|
-
if (not isinstance(verifyUserContext, bool)):
|
|
10542
|
-
verifyUserContext = True
|
|
10543
|
-
|
|
10544
|
-
# determine if the device value specified is a name (e.g. "Bose-ST10-1") or
|
|
10545
|
-
# an id (e.g. "30fbc80e35598f3c242f2120413c943dfd9715fe", "48b677ca-ef9b-516f-b702-93bf2e8c67ba").
|
|
10546
|
-
isDeviceId:bool = SpotifyClient.IsDeviceId(deviceValue)
|
|
10547
|
-
|
|
10548
|
-
deviceValueCompare:str = deviceValue.lower()
|
|
10549
|
-
deviceActiveUser:str = None
|
|
10550
|
-
discoverResult:ZeroconfDiscoveryResult
|
|
10551
|
-
info:ZeroconfGetInfo
|
|
10552
|
-
scDevice:SpotifyConnectDevice
|
|
10553
|
-
|
|
10554
|
-
# get all Spotify Connect available devices on the network.
|
|
10555
|
-
scDevices:SpotifyConnectDevices = self.GetSpotifyConnectDevices()
|
|
10556
|
-
|
|
10557
|
-
# process all discovered devices.
|
|
10558
|
-
for scDevice in scDevices:
|
|
10559
|
-
|
|
10560
|
-
discoverResult = scDevice.DiscoveryResult
|
|
10561
|
-
info = scDevice.DeviceInfo
|
|
10562
|
-
|
|
10563
|
-
# store the currently active user of the device, in case we need to switch users later on.
|
|
10564
|
-
deviceActiveUser = info.ActiveUser.lower()
|
|
10565
|
-
|
|
10566
|
-
# are aliases being used (RemoteName is null if so)?
|
|
10567
|
-
if info.HasAliases:
|
|
10568
|
-
|
|
10569
|
-
# if aliases are defined for the device, then we have to check each alias for a match.
|
|
10570
|
-
infoAlias:ZeroconfGetInfoAlias
|
|
10571
|
-
for infoAlias in info.Aliases:
|
|
10572
|
-
|
|
10573
|
-
# if input deviceValue is a device id then compare the id; otherwise, compare the name.
|
|
10574
|
-
if isDeviceId:
|
|
10575
|
-
if (infoAlias.Id.lower() == deviceValueCompare):
|
|
10576
|
-
_logsi.LogVerbose("Device value '%s' was matched to device Alias Id '%s' (%s)" % (deviceValue, infoAlias.Name, infoAlias.Id))
|
|
10577
|
-
deviceIdResult = infoAlias.Id
|
|
10578
|
-
break # alias processing loop
|
|
10579
|
-
else:
|
|
10580
|
-
if (infoAlias.Name.lower() == deviceValueCompare):
|
|
10581
|
-
_logsi.LogVerbose("Device value '%s' was matched to device Alias Name '%s' (%s)" % (deviceValue, infoAlias.Name, infoAlias.Id))
|
|
10582
|
-
deviceIdResult = infoAlias.Id
|
|
10583
|
-
break # alias processing loop
|
|
10584
|
-
|
|
10585
|
-
else:
|
|
10586
|
-
|
|
10587
|
-
# if input deviceValue is a device id then compare the id; otherwise, compare the name.
|
|
10588
|
-
if isDeviceId:
|
|
10589
|
-
if (info.DeviceId.lower() == deviceValueCompare):
|
|
10590
|
-
_logsi.LogVerbose("Device value '%s' was matched to device Id '%s' (%s)" % (deviceValue, info.RemoteName, info.DeviceId))
|
|
10591
|
-
deviceIdResult = info.DeviceId
|
|
10592
|
-
else:
|
|
10593
|
-
if (info.RemoteName.lower() == deviceValueCompare):
|
|
10594
|
-
_logsi.LogVerbose("Device value '%s' was matched to device RemoteName '%s' (%s)" % (deviceValue, info.RemoteName, info.DeviceId))
|
|
10595
|
-
deviceIdResult = info.DeviceId
|
|
10596
|
-
|
|
10597
|
-
# did we resolve the device id?
|
|
10598
|
-
if deviceIdResult is not None:
|
|
10599
|
-
|
|
10600
|
-
# is this a dynamic device? if so, then we can't control it.
|
|
10601
|
-
if (discoverResult.IsDynamicDevice):
|
|
10602
|
-
_logsi.LogVerbose("Spotify Connect Device '%s' (%s) is a dynamic device; we cannot control it" % (info.RemoteName, deviceIdResult))
|
|
10603
|
-
break
|
|
10604
|
-
|
|
10605
|
-
# did caller request user context switch bypass?
|
|
10606
|
-
# if so, then just return the device id.
|
|
10607
|
-
if (not verifyUserContext):
|
|
10608
|
-
_logsi.LogVerbose("Spotify Connect user context '%s' will be used for Device id '%s', as user chose to bypass user context switch" % (deviceActiveUser, deviceIdResult))
|
|
10609
|
-
break
|
|
10610
|
-
|
|
10611
|
-
# was a Spotify Connect user account specified on the class constructor?
|
|
10612
|
-
# if not, then do not switch the user context.
|
|
10613
|
-
if (self._SpotifyConnectUsername is None):
|
|
10614
|
-
_logsi.LogVerbose("Spotify Connect user context '%s' will be used for Device id '%s', as a SpotifyConnectUsername parameter was not supplied" % (deviceActiveUser, deviceIdResult))
|
|
10615
|
-
break
|
|
10616
|
-
|
|
10617
|
-
# does our Spotify Connect user account need to take control of the device?
|
|
10618
|
-
# if not, then we are done.
|
|
10619
|
-
# this takes into account if the device is in the active device list, as well
|
|
10620
|
-
# as if the active user is not our user context.
|
|
10621
|
-
if (info.IsInDeviceList == True) \
|
|
10622
|
-
and ((deviceActiveUser == self._SpotifyConnectUsername.lower()) or (deviceActiveUser == self.UserProfile.Id.lower())):
|
|
10623
|
-
_logsi.LogVerbose("Spotify Connect user context '%s' was verified for Device id '%s'; switch not necessary" % (deviceActiveUser, deviceIdResult))
|
|
10624
|
-
break
|
|
10625
|
-
|
|
10626
|
-
# is this a Sonos device with no active user?
|
|
10627
|
-
# if so, then don't bother trying to switch the user context as the Sonos
|
|
10628
|
-
# device is a "restricted" type and cannot be controlled via Spotify WebServices API.
|
|
10629
|
-
if (info.IsBrandSonos) and (not info.HasActiveUser):
|
|
10630
|
-
_logsi.LogVerbose("Spotify Connect Device id '%s' is a Sonos device; user account switching is not supported" % (deviceIdResult))
|
|
10631
|
-
break
|
|
10632
|
-
|
|
10633
|
-
zcfResult:ZeroconfResponse
|
|
10634
|
-
|
|
10635
|
-
# create a ZeroconfConnect object to access the device.
|
|
10636
|
-
# note that we are using the "HostIpAddress" property value here, with "Server" as a fallback.
|
|
10637
|
-
# the "Server" property is an alias, which must be resolved via a DNS lookup under
|
|
10638
|
-
# the covers and adds a significant delay (2-3 seconds!) to the activation time.
|
|
10639
|
-
zconn:ZeroconfConnect = ZeroconfConnect(discoverResult.HostIpAddress,
|
|
10640
|
-
discoverResult.HostIpPort,
|
|
10641
|
-
discoverResult.SpotifyConnectCPath,
|
|
10642
|
-
useSSL=False,
|
|
10643
|
-
tokenStorageDir=self.TokenStorageDir)
|
|
10644
|
-
|
|
10645
|
-
# if a different user context has control of the device then we need to disconnect
|
|
10646
|
-
# the current user context before connecting a different user.
|
|
10647
|
-
if (info.HasActiveUser):
|
|
10648
|
-
_logsi.LogVerbose("Spotify Connect user context '%s' is being disconnected for Device id '%s'" % (deviceActiveUser, deviceIdResult))
|
|
10649
|
-
zcfResult = zconn.Disconnect(delay)
|
|
10650
|
-
|
|
10651
|
-
# connect the device to OUR Spotify Connect user context.
|
|
10652
|
-
# note that the result here only indicates that the connect was submitted - NOT that it was successful!
|
|
10653
|
-
_logsi.LogVerbose("Spotify Connect user context '%s' is being connected for Device id '%s'" % (self._SpotifyConnectUsername, deviceIdResult))
|
|
10654
|
-
zcfResult = zconn.Connect(self._SpotifyConnectUsername, self._SpotifyConnectPassword, self._SpotifyConnectLoginId)
|
|
10655
|
-
|
|
10656
|
-
# trace.
|
|
10657
|
-
_logsi.LogVerbose("Spotify Connect user context switch from '%s' to '%s' was requested for Device id '%s'" % (deviceActiveUser, self._SpotifyConnectUsername, deviceIdResult))
|
|
10658
|
-
|
|
10659
|
-
loopTotalDelay:float = 0
|
|
10660
|
-
LOOP_DELAY:float = 0.25
|
|
10661
|
-
while True:
|
|
10662
|
-
|
|
10663
|
-
# wait just a bit between active device list queries.
|
|
10664
|
-
_logsi.LogVerbose(TRACE_MSG_DELAY_DEVICE % LOOP_DELAY)
|
|
10665
|
-
time.sleep(LOOP_DELAY)
|
|
10666
|
-
loopTotalDelay = loopTotalDelay + LOOP_DELAY
|
|
10667
|
-
|
|
10668
|
-
# check for the device in the active device list.
|
|
10669
|
-
# if we find it, then we are done.
|
|
10670
|
-
device:Device = self.GetPlayerDevice(deviceIdResult, True)
|
|
10671
|
-
if device is not None:
|
|
10672
|
-
_logsi.LogVerbose("Spotify Connect Device id '%s' is now in the active device list; device found within %f seconds of Connect" % (deviceIdResult, loopTotalDelay))
|
|
10673
|
-
break
|
|
10674
|
-
|
|
10675
|
-
# only check so many times before we give up;
|
|
10676
|
-
# we will keep checking until we find the device, or we exceed the verifyTimeout value.
|
|
10677
|
-
if (loopTotalDelay > verifyTimeout):
|
|
10678
|
-
_logsi.LogWarning("Verification timeout waiting for Spotify Connect Device id '%s' to be added to the active device list; device not found within %f seconds of Connect" % (deviceIdResult, verifyTimeout))
|
|
10679
|
-
break
|
|
10680
|
-
|
|
10681
|
-
# end the device processing loop.
|
|
10682
|
-
break
|
|
10683
|
-
|
|
10684
|
-
# did we resolve the device id?
|
|
10685
|
-
if deviceIdResult is None:
|
|
10686
|
-
_logsi.LogWarning("Spotify Connect device name '%s' could not be resolved; please ensure that the specified device is powered on and available on the local network" % (deviceValue))
|
|
10687
|
-
|
|
10688
|
-
# return the resolved device id.
|
|
10689
|
-
return deviceIdResult
|
|
10918
|
+
# otherwise return the device id value.
|
|
10919
|
+
return scDevice.Id
|
|
10690
10920
|
|
|
10691
10921
|
except SpotifyZeroconfApiError: raise # pass handled exceptions on thru
|
|
10692
10922
|
except SpotifyApiError: raise # pass handled exceptions on thru
|
|
@@ -11003,7 +11233,7 @@ class SpotifyClient:
|
|
|
11003
11233
|
deviceId:str=None,
|
|
11004
11234
|
play:bool=True,
|
|
11005
11235
|
delay:float=0.50,
|
|
11006
|
-
|
|
11236
|
+
refreshDeviceList:bool=False,
|
|
11007
11237
|
) -> None:
|
|
11008
11238
|
"""
|
|
11009
11239
|
Transfer playback to a new Spotify Connect device and optionally begin playback.
|
|
@@ -11025,9 +11255,9 @@ class SpotifyClient:
|
|
|
11025
11255
|
This delay will give the spotify web api time to process the change before
|
|
11026
11256
|
another command is issued.
|
|
11027
11257
|
Default is 0.50; value range is 0 - 10.
|
|
11028
|
-
|
|
11029
|
-
True to
|
|
11030
|
-
|
|
11258
|
+
refreshDeviceList (bool):
|
|
11259
|
+
True to refresh the Spotify Connect device list; otherwise, False to use the
|
|
11260
|
+
Spotify Connect device list cache.
|
|
11031
11261
|
|
|
11032
11262
|
Raises:
|
|
11033
11263
|
SpotifyWebApiError:
|
|
@@ -11049,6 +11279,7 @@ class SpotifyClient:
|
|
|
11049
11279
|
"""
|
|
11050
11280
|
apiMethodName:str = 'PlayerTransferPlayback'
|
|
11051
11281
|
apiMethodParms:SIMethodParmListContext = None
|
|
11282
|
+
scDevice:SpotifyConnectDevice = None
|
|
11052
11283
|
|
|
11053
11284
|
try:
|
|
11054
11285
|
|
|
@@ -11057,18 +11288,32 @@ class SpotifyClient:
|
|
|
11057
11288
|
apiMethodParms.AppendKeyValue("deviceId", deviceId)
|
|
11058
11289
|
apiMethodParms.AppendKeyValue("play", play)
|
|
11059
11290
|
apiMethodParms.AppendKeyValue("delay", delay)
|
|
11060
|
-
apiMethodParms.AppendKeyValue("
|
|
11291
|
+
apiMethodParms.AppendKeyValue("refreshDeviceList", refreshDeviceList)
|
|
11061
11292
|
_logsi.LogMethodParmList(SILevel.Verbose, "Transfer playback to a new Spotify Connect device", apiMethodParms)
|
|
11062
11293
|
|
|
11063
11294
|
# validations.
|
|
11064
11295
|
delay = validateDelay(delay, 0.50, 10)
|
|
11065
11296
|
|
|
11066
|
-
#
|
|
11067
|
-
|
|
11068
|
-
|
|
11069
|
-
|
|
11070
|
-
|
|
11071
|
-
|
|
11297
|
+
# ensure the specified device id / name is active and available, and
|
|
11298
|
+
# the user context is set correctly.
|
|
11299
|
+
scDevice = self.GetSpotifyConnectDevice(
|
|
11300
|
+
deviceId,
|
|
11301
|
+
verifyUserContext=True,
|
|
11302
|
+
refreshDeviceList=refreshDeviceList,
|
|
11303
|
+
activateDevice=True)
|
|
11304
|
+
|
|
11305
|
+
# did we find the device?
|
|
11306
|
+
# if so, then use the found device id in case a device name was specified.
|
|
11307
|
+
if scDevice is not None:
|
|
11308
|
+
|
|
11309
|
+
deviceId = scDevice.Id
|
|
11310
|
+
|
|
11311
|
+
# is this a Sonos device?
|
|
11312
|
+
# if so, then control was automatically transferred by activating the device.
|
|
11313
|
+
# it's a restricted device, so the Spotify Web API cannot control it!
|
|
11314
|
+
if scDevice.DeviceInfo.IsBrandSonos:
|
|
11315
|
+
_logsi.LogVerbose('Sonos device detected; bypassing call to Spotify Web API Transfer Playback endpoint')
|
|
11316
|
+
return
|
|
11072
11317
|
|
|
11073
11318
|
# build spotify web api request parameters.
|
|
11074
11319
|
reqData:dict = \
|