spotifywebapiPython 1.0.79__py3-none-any.whl → 1.0.81__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.79.dist-info → spotifywebapiPython-1.0.81.dist-info}/METADATA +1 -1
- {spotifywebapiPython-1.0.79.dist-info → spotifywebapiPython-1.0.81.dist-info}/RECORD +24 -24
- spotifywebapipython/const.py +1 -1
- spotifywebapipython/docs/__init__.html +18 -3
- spotifywebapipython/docs/index.html +2 -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 +2 -2
- 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 +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/spotifyclient.py +437 -195
- spotifywebapipython/zeroconfapi/zeroconfconnect.py +4 -2
- spotifywebapipython/zeroconfapi/zeroconfgetinfoalias.py +8 -0
- {spotifywebapiPython-1.0.79.dist-info → spotifywebapiPython-1.0.81.dist-info}/LICENSE +0 -0
- {spotifywebapiPython-1.0.79.dist-info → spotifywebapiPython-1.0.81.dist-info}/WHEEL +0 -0
- {spotifywebapiPython-1.0.79.dist-info → spotifywebapiPython-1.0.81.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,296 @@ 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 `SpotifyConnectDevice` 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
|
+
# are aliases being used (RemoteName is null if so)?
|
|
8134
|
+
if info.HasAliases:
|
|
8135
|
+
|
|
8136
|
+
# if aliases are defined for the device, then we have to check each alias for a match.
|
|
8137
|
+
infoAlias:ZeroconfGetInfoAlias
|
|
8138
|
+
for infoAlias in info.Aliases:
|
|
8139
|
+
|
|
8140
|
+
# if input deviceValue is a device id then compare the id; otherwise, compare the name.
|
|
8141
|
+
if isDeviceId:
|
|
8142
|
+
if (infoAlias.Id.lower() == deviceValueCompare):
|
|
8143
|
+
_logsi.LogVerbose("Device value '%s' was matched to device Alias: %s" % (deviceValue, infoAlias.Title))
|
|
8144
|
+
deviceResultId = infoAlias.Id
|
|
8145
|
+
deviceResult = scDevice
|
|
8146
|
+
break # alias processing loop
|
|
8147
|
+
else:
|
|
8148
|
+
if (infoAlias.Name.lower() == deviceValueCompare):
|
|
8149
|
+
_logsi.LogVerbose("Device value '%s' was matched to device Alias: %s" % (deviceValue, infoAlias.Title))
|
|
8150
|
+
deviceResultId = infoAlias.Id
|
|
8151
|
+
deviceResult = scDevice
|
|
8152
|
+
break # alias processing loop
|
|
8153
|
+
|
|
8154
|
+
else:
|
|
8155
|
+
|
|
8156
|
+
# if input deviceValue is a device id then compare the id; otherwise, compare the name.
|
|
8157
|
+
if isDeviceId:
|
|
8158
|
+
if (scDevice.Id.lower() == deviceValueCompare):
|
|
8159
|
+
_logsi.LogVerbose("Device value '%s' was matched to device: '%s'" % (deviceValue, scDevice.Title))
|
|
8160
|
+
deviceResultId = scDevice.Id
|
|
8161
|
+
deviceResult = scDevice
|
|
8162
|
+
else:
|
|
8163
|
+
if (scDevice.Name.lower() == deviceValueCompare):
|
|
8164
|
+
_logsi.LogVerbose("Device value '%s' was matched to device: '%s'" % (deviceValue, scDevice.Title))
|
|
8165
|
+
deviceResultId = scDevice.Id
|
|
8166
|
+
deviceResult = scDevice
|
|
8167
|
+
|
|
8168
|
+
# did we resolve the device id?
|
|
8169
|
+
if deviceResult is not None:
|
|
8170
|
+
|
|
8171
|
+
# at this point, we will try to activate the device if it requires it.
|
|
8172
|
+
|
|
8173
|
+
# reset device reconnected flag.
|
|
8174
|
+
scDevice.WasReConnected = False
|
|
8175
|
+
|
|
8176
|
+
# create a ZeroconfConnect object to access the device.
|
|
8177
|
+
# note that we are using the "HostIpAddress" property value here, with "Server" as a fallback.
|
|
8178
|
+
# the "Server" property is an alias, which must be resolved via a DNS lookup under
|
|
8179
|
+
# the covers and adds a significant delay (2-3 seconds!) to the activation time.
|
|
8180
|
+
zconn:ZeroconfConnect = ZeroconfConnect(discoverResult.HostIpAddress,
|
|
8181
|
+
discoverResult.HostIpPort,
|
|
8182
|
+
discoverResult.SpotifyConnectCPath,
|
|
8183
|
+
useSSL=False,
|
|
8184
|
+
tokenStorageDir=self.TokenStorageDir)
|
|
8185
|
+
|
|
8186
|
+
# if we did not refresh the device list, then query the device for real-time status; just in
|
|
8187
|
+
# case the device was disconnected after the last cache refresh.
|
|
8188
|
+
if not refreshDeviceList:
|
|
8189
|
+
_logsi.LogVerbose("Getting real-time status information for Spotify Connect device: '%s'" % (deviceResult.Title))
|
|
8190
|
+
info = zconn.GetInformation()
|
|
8191
|
+
|
|
8192
|
+
# store the currently active user of the device, in case we need to switch users later on.
|
|
8193
|
+
deviceActiveUser = info.ActiveUser.lower()
|
|
8194
|
+
|
|
8195
|
+
# was device activation requested? if not, then we are done.
|
|
8196
|
+
if activateDevice == False:
|
|
8197
|
+
_logsi.LogVerbose("Activation not requested for Spotify Connect device: '%s'" % (deviceResult.Title))
|
|
8198
|
+
break
|
|
8199
|
+
|
|
8200
|
+
# is this a dynamic device? if so, then there is no need to activate it as it's already active.
|
|
8201
|
+
if (discoverResult.IsDynamicDevice):
|
|
8202
|
+
_logsi.LogVerbose("Activation not required for dynamic Spotify Connect device: '%s'" % (deviceResult.Title))
|
|
8203
|
+
break
|
|
8204
|
+
|
|
8205
|
+
# did caller request user context switch bypass?
|
|
8206
|
+
# if so, then just return the device.
|
|
8207
|
+
if (not verifyUserContext):
|
|
8208
|
+
_logsi.LogVerbose("User context '%s' will be used for Spotify Connect device '%s', as user chose to bypass user context switch" % (deviceActiveUser, deviceResult.Title))
|
|
8209
|
+
break
|
|
8210
|
+
|
|
8211
|
+
# was a Spotify Connect user account specified on the class constructor?
|
|
8212
|
+
# if not, then do not switch the user context.
|
|
8213
|
+
if (self._SpotifyConnectUsername is None):
|
|
8214
|
+
_logsi.LogVerbose("User context '%s' will be used for Spotify Connect device '%s', as a SpotifyConnectUsername parameter was not supplied" % (deviceActiveUser, deviceResult.Title))
|
|
8215
|
+
break
|
|
8216
|
+
|
|
8217
|
+
zcfResult:ZeroconfResponse
|
|
8218
|
+
|
|
8219
|
+
# if a different user context has control of the device then we need to disconnect
|
|
8220
|
+
# the current user context before connecting a different user.
|
|
8221
|
+
if (info.HasActiveUser):
|
|
8222
|
+
_logsi.LogVerbose("Issuing Disconnect to Spotify Connect device '%s' for user context '%s'" % (deviceResult.Title, deviceActiveUser))
|
|
8223
|
+
zcfResult = zconn.Disconnect(delay)
|
|
8224
|
+
|
|
8225
|
+
# connect the device to OUR Spotify Connect user context.
|
|
8226
|
+
# note that the result here only indicates that the connect was submitted - NOT that it was successful!
|
|
8227
|
+
_logsi.LogVerbose("Issuing Connect to Spotify Connect device '%s' for user context '%s'" % (deviceResult.Title, self._SpotifyConnectUsername))
|
|
8228
|
+
zcfResult = zconn.Connect(self._SpotifyConnectUsername, self._SpotifyConnectPassword, self._SpotifyConnectLoginId)
|
|
8229
|
+
|
|
8230
|
+
# indicate device was reconnected.
|
|
8231
|
+
deviceResult.WasReConnected = True
|
|
8232
|
+
|
|
8233
|
+
# trace.
|
|
8234
|
+
_logsi.LogVerbose("User context switch from '%s' to '%s' was requested for Spotify Connect device: '%s'" % (deviceActiveUser, self._SpotifyConnectUsername, deviceResult.Title))
|
|
8235
|
+
|
|
8236
|
+
loopTotalDelay:float = 0
|
|
8237
|
+
LOOP_DELAY:float = 0.25
|
|
8238
|
+
while True:
|
|
8239
|
+
|
|
8240
|
+
# wait just a bit between available device list queries.
|
|
8241
|
+
_logsi.LogVerbose(TRACE_MSG_DELAY_DEVICE % LOOP_DELAY)
|
|
8242
|
+
time.sleep(LOOP_DELAY)
|
|
8243
|
+
loopTotalDelay = loopTotalDelay + LOOP_DELAY
|
|
8244
|
+
|
|
8245
|
+
# check for the device in the available device list.
|
|
8246
|
+
# if we find it, then we are done.
|
|
8247
|
+
device:Device = self.GetPlayerDevice(deviceResultId, True)
|
|
8248
|
+
if device is not None:
|
|
8249
|
+
_logsi.LogVerbose("Spotify Connect device '%s' is now in the available device list; device found within %f seconds of Connect" % (deviceResult.Title, loopTotalDelay))
|
|
8250
|
+
break
|
|
8251
|
+
|
|
8252
|
+
# check if the active device is restricted; if it is, then it may not show up in the
|
|
8253
|
+
# available device list of devices (common issue with Sonos devices). in this case,
|
|
8254
|
+
# it WILL show up in the player state device property as the active device.
|
|
8255
|
+
# note that the PlayerPlayState device is a name and not a device id.
|
|
8256
|
+
playerState:PlayerPlayState = self.GetPlayerPlaybackState()
|
|
8257
|
+
if playerState.Device is not None:
|
|
8258
|
+
device = playerState.Device
|
|
8259
|
+
if (device.Id == deviceResultId) or (device.Name == deviceResultId):
|
|
8260
|
+
_logsi.LogVerbose("Spotify Connect device '%s' is now the active device; device found within %f seconds of Connect" % (deviceResult.Title, loopTotalDelay))
|
|
8261
|
+
break
|
|
8262
|
+
|
|
8263
|
+
# only check so many times before we give up;
|
|
8264
|
+
# we will keep checking until we find the device, or we exceed the verifyTimeout value.
|
|
8265
|
+
if (loopTotalDelay > verifyTimeout):
|
|
8266
|
+
_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))
|
|
8267
|
+
break
|
|
8268
|
+
|
|
8269
|
+
# end the device processing loop.
|
|
8270
|
+
break
|
|
8271
|
+
|
|
8272
|
+
# did we resolve the device id?
|
|
8273
|
+
if deviceResult is None:
|
|
8274
|
+
_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))
|
|
8275
|
+
|
|
8276
|
+
# return the resolved device id.
|
|
8277
|
+
return deviceResult
|
|
8278
|
+
|
|
8279
|
+
except SpotifyZeroconfApiError: raise # pass handled exceptions on thru
|
|
8280
|
+
except SpotifyApiError: raise # pass handled exceptions on thru
|
|
8281
|
+
except Exception as ex:
|
|
8282
|
+
|
|
8283
|
+
# format unhandled exception.
|
|
8284
|
+
raise SpotifyApiError(SAAppMessages.UNHANDLED_EXCEPTION.format(apiMethodName, str(ex)), ex, logsi=_logsi)
|
|
8285
|
+
|
|
8286
|
+
finally:
|
|
8287
|
+
|
|
8288
|
+
# trace.
|
|
8289
|
+
_logsi.LeaveMethod(SILevel.Debug, apiMethodName)
|
|
8290
|
+
|
|
8291
|
+
|
|
7987
8292
|
def GetSpotifyConnectDevices(
|
|
7988
8293
|
self,
|
|
7989
|
-
refresh:bool=True
|
|
8294
|
+
refresh:bool=True,
|
|
8295
|
+
sortResult:bool=True,
|
|
7990
8296
|
) -> SpotifyConnectDevices:
|
|
7991
8297
|
"""
|
|
7992
8298
|
Get information about all available Spotify Connect player devices.
|
|
@@ -7997,6 +8303,10 @@ class SpotifyClient:
|
|
|
7997
8303
|
refresh (bool):
|
|
7998
8304
|
True (default) to return real-time information from the spotify zeroconf api and
|
|
7999
8305
|
update the cache; otherwise, False to just return the cached value.
|
|
8306
|
+
sortResult (bool):
|
|
8307
|
+
True to sort the items by name; otherwise, False to leave the items in the same order they
|
|
8308
|
+
were returned in by the Spotify Web API.
|
|
8309
|
+
Default: True
|
|
8000
8310
|
|
|
8001
8311
|
Returns:
|
|
8002
8312
|
A `SpotifyConnectDevices` object that contain the discovery results
|
|
@@ -8038,20 +8348,24 @@ class SpotifyClient:
|
|
|
8038
8348
|
</details>
|
|
8039
8349
|
"""
|
|
8040
8350
|
apiMethodName:str = 'GetSpotifyConnectDevices'
|
|
8351
|
+
apiMethodParms:SIMethodParmListContext = None
|
|
8041
8352
|
result:SpotifyConnectDevices = SpotifyConnectDevices()
|
|
8042
8353
|
cacheDesc:str = CACHE_SOURCE_CURRENT
|
|
8043
8354
|
|
|
8044
8355
|
try:
|
|
8045
8356
|
|
|
8046
8357
|
# trace.
|
|
8047
|
-
_logsi.
|
|
8358
|
+
apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
|
|
8359
|
+
apiMethodParms.AppendKeyValue("refresh", refresh)
|
|
8360
|
+
apiMethodParms.AppendKeyValue("sortResult", sortResult)
|
|
8361
|
+
_logsi.LogMethodParmList(SILevel.Verbose, "Get available Spotify Connect devices", apiMethodParms)
|
|
8048
8362
|
|
|
8049
8363
|
# can we use the cached value?
|
|
8050
8364
|
if (not refresh) and (apiMethodName in self._ConfigurationCache):
|
|
8051
8365
|
|
|
8052
8366
|
result = self._ConfigurationCache[apiMethodName]
|
|
8053
8367
|
cacheDesc = CACHE_SOURCE_CACHED
|
|
8054
|
-
|
|
8368
|
+
|
|
8055
8369
|
else:
|
|
8056
8370
|
|
|
8057
8371
|
# discover Spotify Connect devices on the network, waiting up to the specified timeout
|
|
@@ -8134,6 +8448,8 @@ class SpotifyClient:
|
|
|
8134
8448
|
|
|
8135
8449
|
# create new spotify connect device object.
|
|
8136
8450
|
scDevice:SpotifyConnectDevice = SpotifyConnectDevice()
|
|
8451
|
+
scDevice.Id = info.DeviceId
|
|
8452
|
+
scDevice.Name = info.RemoteName
|
|
8137
8453
|
scDevice.DiscoveryResult = discoverResult
|
|
8138
8454
|
scDevice.DeviceInfo = info
|
|
8139
8455
|
result.Items.append(scDevice)
|
|
@@ -8151,9 +8467,10 @@ class SpotifyClient:
|
|
|
8151
8467
|
|
|
8152
8468
|
# is this a dynamic device?
|
|
8153
8469
|
if not result.ContainsDeviceId(device.Id):
|
|
8470
|
+
_logsi.LogVerbose("Spotify Connect dynamic device detected: '%s' (%s)" % (device.Name, device.Id))
|
|
8154
8471
|
result.AddDynamicDevice(device, self.UserProfile.Id)
|
|
8155
8472
|
|
|
8156
|
-
# is the device in the
|
|
8473
|
+
# is the device in the available device list for this user context?
|
|
8157
8474
|
# if not, then set an indicator so we can re-activate it later if need be.
|
|
8158
8475
|
scDevice:SpotifyConnectDevice
|
|
8159
8476
|
for scDevice in result:
|
|
@@ -8161,10 +8478,30 @@ class SpotifyClient:
|
|
|
8161
8478
|
scDevice.DeviceInfo.IsInDeviceList = True
|
|
8162
8479
|
scDevice.DeviceInfo.IsActiveDevice = device.IsActive
|
|
8163
8480
|
break
|
|
8481
|
+
|
|
8482
|
+
# get the currently active device (if any).
|
|
8483
|
+
# we do this in case the active device is a "restricted" device; if it is, then it may
|
|
8484
|
+
# not show up as the active device (common issue with Sonos devices).
|
|
8485
|
+
# in this case, it WILL show up in the player state device property as the active device.
|
|
8486
|
+
# if it IS active, then we will set the active flag.
|
|
8487
|
+
# note that the PlayerPlayState device is a name and not a device id.
|
|
8488
|
+
playerState:PlayerPlayState = self.GetPlayerPlaybackState()
|
|
8489
|
+
if playerState.Device is not None:
|
|
8490
|
+
device = playerState.Device
|
|
8491
|
+
scDevice:SpotifyConnectDevice
|
|
8492
|
+
for scDevice in result:
|
|
8493
|
+
if (device.Name == scDevice.Name):
|
|
8494
|
+
_logsi.LogVerbose("Spotify Connect active device detected: '%s' (%s)" % (device.Name, device.Id))
|
|
8495
|
+
scDevice.DeviceInfo.IsActiveDevice = True
|
|
8496
|
+
break
|
|
8164
8497
|
|
|
8165
8498
|
# update cache.
|
|
8166
8499
|
self._ConfigurationCache[apiMethodName] = result
|
|
8167
8500
|
|
|
8501
|
+
# sort items on Name property, ascending order.
|
|
8502
|
+
if (len(result.Items) > 0) and (sortResult is True):
|
|
8503
|
+
result.Items.sort(key=lambda x: (x.Name or "").lower(), reverse=False)
|
|
8504
|
+
|
|
8168
8505
|
# update last refresh date.
|
|
8169
8506
|
result.DateLastRefreshed = datetime.utcnow().timestamp()
|
|
8170
8507
|
|
|
@@ -9728,7 +10065,8 @@ class SpotifyClient:
|
|
|
9728
10065
|
Default is 0.50; value range is 0 - 10.
|
|
9729
10066
|
resolveDeviceId (bool):
|
|
9730
10067
|
True to resolve the supplied `deviceId` value; otherwise, False not resolve the `deviceId`
|
|
9731
|
-
value as it has already been resolved.
|
|
10068
|
+
value as it has already been resolved.
|
|
10069
|
+
Default is True.
|
|
9732
10070
|
|
|
9733
10071
|
Raises:
|
|
9734
10072
|
SpotifyWebApiError:
|
|
@@ -9786,8 +10124,17 @@ class SpotifyClient:
|
|
|
9786
10124
|
|
|
9787
10125
|
# ensure the specified device id / name is active and available, and
|
|
9788
10126
|
# the user context is set correctly.
|
|
9789
|
-
|
|
9790
|
-
|
|
10127
|
+
scDevice = self.GetSpotifyConnectDevice(
|
|
10128
|
+
deviceId,
|
|
10129
|
+
refreshDeviceList=False,
|
|
10130
|
+
activateDevice=True,
|
|
10131
|
+
verifyUserContext=True)
|
|
10132
|
+
|
|
10133
|
+
# did we find the device?
|
|
10134
|
+
# if so, then use the found device id in case a device name was specified.
|
|
10135
|
+
if scDevice is not None:
|
|
10136
|
+
deviceId = scDevice.Id
|
|
10137
|
+
|
|
9791
10138
|
# build spotify web api request parameters.
|
|
9792
10139
|
reqData:dict = \
|
|
9793
10140
|
{
|
|
@@ -9860,7 +10207,8 @@ class SpotifyClient:
|
|
|
9860
10207
|
Default is 0.50; value range is 0 - 10.
|
|
9861
10208
|
resolveDeviceId (bool):
|
|
9862
10209
|
True to resolve the supplied `deviceId` value; otherwise, False not resolve the `deviceId`
|
|
9863
|
-
value as it has already been resolved.
|
|
10210
|
+
value as it has already been resolved.
|
|
10211
|
+
Default is True.
|
|
9864
10212
|
limitTotal (int):
|
|
9865
10213
|
The maximum number of items to retrieve from favorites.
|
|
9866
10214
|
Default: 200
|
|
@@ -9918,7 +10266,16 @@ class SpotifyClient:
|
|
|
9918
10266
|
|
|
9919
10267
|
# ensure the specified device id / name is active and available, and
|
|
9920
10268
|
# the user context is set correctly.
|
|
9921
|
-
|
|
10269
|
+
scDevice = self.GetSpotifyConnectDevice(
|
|
10270
|
+
deviceId,
|
|
10271
|
+
refreshDeviceList=False,
|
|
10272
|
+
activateDevice=True,
|
|
10273
|
+
verifyUserContext=True)
|
|
10274
|
+
|
|
10275
|
+
# did we find the device?
|
|
10276
|
+
# if so, then use the found device id in case a device name was specified.
|
|
10277
|
+
if scDevice is not None:
|
|
10278
|
+
deviceId = scDevice.Id
|
|
9922
10279
|
|
|
9923
10280
|
# set desired shuffle mode.
|
|
9924
10281
|
if shuffle is not None:
|
|
@@ -9984,7 +10341,8 @@ class SpotifyClient:
|
|
|
9984
10341
|
Default is 0.50; value range is 0 - 10.
|
|
9985
10342
|
resolveDeviceId (bool):
|
|
9986
10343
|
True to resolve the supplied `deviceId` value; otherwise, False not resolve the `deviceId`
|
|
9987
|
-
value as it has already been resolved.
|
|
10344
|
+
value as it has already been resolved.
|
|
10345
|
+
Default is True.
|
|
9988
10346
|
|
|
9989
10347
|
Raises:
|
|
9990
10348
|
SpotifyWebApiError:
|
|
@@ -10036,7 +10394,16 @@ class SpotifyClient:
|
|
|
10036
10394
|
|
|
10037
10395
|
# ensure the specified device id / name is active and available, and
|
|
10038
10396
|
# the user context is set correctly.
|
|
10039
|
-
|
|
10397
|
+
scDevice = self.GetSpotifyConnectDevice(
|
|
10398
|
+
deviceId,
|
|
10399
|
+
refreshDeviceList=False,
|
|
10400
|
+
activateDevice=True,
|
|
10401
|
+
verifyUserContext=True)
|
|
10402
|
+
|
|
10403
|
+
# did we find the device?
|
|
10404
|
+
# if so, then use the found device id in case a device name was specified.
|
|
10405
|
+
if scDevice is not None:
|
|
10406
|
+
deviceId = scDevice.Id
|
|
10040
10407
|
|
|
10041
10408
|
# build spotify web api request parameters.
|
|
10042
10409
|
reqData:dict = \
|
|
@@ -10455,6 +10822,9 @@ class SpotifyClient:
|
|
|
10455
10822
|
delay:float=0.25
|
|
10456
10823
|
) -> str:
|
|
10457
10824
|
"""
|
|
10825
|
+
This method has been deprecated.
|
|
10826
|
+
Use the `GetSpotifyConnectDevice` method instead.
|
|
10827
|
+
|
|
10458
10828
|
Resolves a Spotify Connect device identifier from a specified device id, name, alias id,
|
|
10459
10829
|
or alias name. This will ensure that the device id can be found on the network, as well
|
|
10460
10830
|
as connect to the device if necessary with the current user context.
|
|
@@ -10508,13 +10878,6 @@ class SpotifyClient:
|
|
|
10508
10878
|
`SpotifyConnectUsername` was not supplied on the class constructor. If the user context is to be
|
|
10509
10879
|
switched, then a Disconnect will be issued if a user context is active on the device followed by
|
|
10510
10880
|
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
10881
|
"""
|
|
10519
10882
|
apiMethodName:str = 'PlayerResolveDeviceId'
|
|
10520
10883
|
apiMethodParms:SIMethodParmListContext = None
|
|
@@ -10522,6 +10885,9 @@ class SpotifyClient:
|
|
|
10522
10885
|
|
|
10523
10886
|
try:
|
|
10524
10887
|
|
|
10888
|
+
# TODO method is deprecated as of 2024/08/15, v1.0.80.
|
|
10889
|
+
# remove in a future release.
|
|
10890
|
+
|
|
10525
10891
|
# trace.
|
|
10526
10892
|
apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
|
|
10527
10893
|
apiMethodParms.AppendKeyValue("deviceValue", deviceValue)
|
|
@@ -10530,163 +10896,24 @@ class SpotifyClient:
|
|
|
10530
10896
|
apiMethodParms.AppendKeyValue("delay", delay)
|
|
10531
10897
|
_logsi.LogMethodParmList(SILevel.Verbose, "Resolving Spotify Connect Player Device Id", apiMethodParms)
|
|
10532
10898
|
|
|
10533
|
-
#
|
|
10534
|
-
|
|
10535
|
-
|
|
10899
|
+
# trace.
|
|
10900
|
+
_logsi.LogWarning("SpotifyWebApiPython `PlayerResolveDeviceId` method is deprecated, and will be removed in a future release; use the `GetSpotifyConnectDevice` instead.")
|
|
10901
|
+
|
|
10902
|
+
# call newer method to resolve the device id.
|
|
10903
|
+
scDevice:SpotifyConnectDevice = self.GetSpotifyConnectDevice(
|
|
10904
|
+
deviceValue=deviceValue,
|
|
10905
|
+
verifyUserContext=verifyUserContext,
|
|
10906
|
+
verifyTimeout=verifyTimeout,
|
|
10907
|
+
refreshDeviceList=True,
|
|
10908
|
+
activateDevice=True,
|
|
10909
|
+
delay=delay)
|
|
10910
|
+
|
|
10911
|
+
# if device not found then we are done.
|
|
10912
|
+
if scDevice is None:
|
|
10536
10913
|
return None
|
|
10537
10914
|
|
|
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
|
|
10915
|
+
# otherwise return the device id value.
|
|
10916
|
+
return scDevice.Id
|
|
10690
10917
|
|
|
10691
10918
|
except SpotifyZeroconfApiError: raise # pass handled exceptions on thru
|
|
10692
10919
|
except SpotifyApiError: raise # pass handled exceptions on thru
|
|
@@ -11003,7 +11230,7 @@ class SpotifyClient:
|
|
|
11003
11230
|
deviceId:str=None,
|
|
11004
11231
|
play:bool=True,
|
|
11005
11232
|
delay:float=0.50,
|
|
11006
|
-
|
|
11233
|
+
refreshDeviceList:bool=False,
|
|
11007
11234
|
) -> None:
|
|
11008
11235
|
"""
|
|
11009
11236
|
Transfer playback to a new Spotify Connect device and optionally begin playback.
|
|
@@ -11025,9 +11252,9 @@ class SpotifyClient:
|
|
|
11025
11252
|
This delay will give the spotify web api time to process the change before
|
|
11026
11253
|
another command is issued.
|
|
11027
11254
|
Default is 0.50; value range is 0 - 10.
|
|
11028
|
-
|
|
11029
|
-
True to
|
|
11030
|
-
|
|
11255
|
+
refreshDeviceList (bool):
|
|
11256
|
+
True to refresh the Spotify Connect device list; otherwise, False to use the
|
|
11257
|
+
Spotify Connect device list cache.
|
|
11031
11258
|
|
|
11032
11259
|
Raises:
|
|
11033
11260
|
SpotifyWebApiError:
|
|
@@ -11049,6 +11276,7 @@ class SpotifyClient:
|
|
|
11049
11276
|
"""
|
|
11050
11277
|
apiMethodName:str = 'PlayerTransferPlayback'
|
|
11051
11278
|
apiMethodParms:SIMethodParmListContext = None
|
|
11279
|
+
scDevice:SpotifyConnectDevice = None
|
|
11052
11280
|
|
|
11053
11281
|
try:
|
|
11054
11282
|
|
|
@@ -11057,18 +11285,32 @@ class SpotifyClient:
|
|
|
11057
11285
|
apiMethodParms.AppendKeyValue("deviceId", deviceId)
|
|
11058
11286
|
apiMethodParms.AppendKeyValue("play", play)
|
|
11059
11287
|
apiMethodParms.AppendKeyValue("delay", delay)
|
|
11060
|
-
apiMethodParms.AppendKeyValue("
|
|
11288
|
+
apiMethodParms.AppendKeyValue("refreshDeviceList", refreshDeviceList)
|
|
11061
11289
|
_logsi.LogMethodParmList(SILevel.Verbose, "Transfer playback to a new Spotify Connect device", apiMethodParms)
|
|
11062
11290
|
|
|
11063
11291
|
# validations.
|
|
11064
11292
|
delay = validateDelay(delay, 0.50, 10)
|
|
11065
11293
|
|
|
11066
|
-
#
|
|
11067
|
-
|
|
11068
|
-
|
|
11069
|
-
|
|
11070
|
-
|
|
11071
|
-
|
|
11294
|
+
# ensure the specified device id / name is active and available, and
|
|
11295
|
+
# the user context is set correctly.
|
|
11296
|
+
scDevice = self.GetSpotifyConnectDevice(
|
|
11297
|
+
deviceId,
|
|
11298
|
+
verifyUserContext=True,
|
|
11299
|
+
refreshDeviceList=refreshDeviceList,
|
|
11300
|
+
activateDevice=True)
|
|
11301
|
+
|
|
11302
|
+
# did we find the device?
|
|
11303
|
+
# if so, then use the found device id in case a device name was specified.
|
|
11304
|
+
if scDevice is not None:
|
|
11305
|
+
|
|
11306
|
+
deviceId = scDevice.Id
|
|
11307
|
+
|
|
11308
|
+
# is this a Sonos device?
|
|
11309
|
+
# if so, then control was automatically transferred by activating the device.
|
|
11310
|
+
# it's a restricted device, so the Spotify Web API cannot control it!
|
|
11311
|
+
if scDevice.DeviceInfo.IsBrandSonos:
|
|
11312
|
+
_logsi.LogVerbose('Sonos device detected; bypassing call to Spotify Web API Transfer Playback endpoint')
|
|
11313
|
+
return
|
|
11072
11314
|
|
|
11073
11315
|
# build spotify web api request parameters.
|
|
11074
11316
|
reqData:dict = \
|