spotifywebapiPython 1.0.150__py3-none-any.whl → 1.0.152__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.150.dist-info → spotifywebapiPython-1.0.152.dist-info}/METADATA +2 -1
- {spotifywebapiPython-1.0.150.dist-info → spotifywebapiPython-1.0.152.dist-info}/RECORD +26 -26
- spotifywebapipython/const.py +6 -1
- spotifywebapipython/docs/__init__.html +15 -2
- 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 +65 -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 +337 -120
- spotifywebapipython/docs/spotifywebapipython/spotifydiscovery.html +2 -2
- spotifywebapipython/docs/spotifywebapipython/spotifywebapiauthenticationerror.html +2 -2
- spotifywebapipython/docs/spotifywebapipython/spotifywebapierror.html +2 -2
- spotifywebapipython/docs/spotifywebapipython/spotifywebplayertoken.html +2 -2
- spotifywebapipython/models/playerplaystate.py +42 -0
- spotifywebapipython/models/spotifyconnectdevice.py +12 -0
- spotifywebapipython/sautils.py +50 -0
- spotifywebapipython/spotifyclient.py +847 -296
- spotifywebapipython/spotifyconnect/spotifyconnectdirectorytask.py +98 -16
- spotifywebapipython/spotifyconnect/spotifyconnectzeroconfexceptions.py +10 -0
- {spotifywebapiPython-1.0.150.dist-info → spotifywebapiPython-1.0.152.dist-info}/LICENSE +0 -0
- {spotifywebapiPython-1.0.150.dist-info → spotifywebapiPython-1.0.152.dist-info}/WHEEL +0 -0
- {spotifywebapiPython-1.0.150.dist-info → spotifywebapiPython-1.0.152.dist-info}/top_level.txt +0 -0
|
@@ -5,10 +5,16 @@ import json
|
|
|
5
5
|
from io import BytesIO
|
|
6
6
|
from oauthlib.oauth2 import BackendApplicationClient, WebApplicationClient
|
|
7
7
|
import socket
|
|
8
|
+
from soco import SoCo
|
|
9
|
+
from soco.core import (
|
|
10
|
+
PLAY_MODE_BY_MEANING as SONOS_PLAY_MODE_BY_MEANING,
|
|
11
|
+
PLAY_MODES as SONOS_PLAY_MODES,
|
|
12
|
+
)
|
|
8
13
|
import time
|
|
9
14
|
from typing import Tuple, Callable, Union
|
|
10
15
|
from urllib3 import PoolManager, Timeout, HTTPResponse
|
|
11
16
|
from urllib.parse import urlencode
|
|
17
|
+
import urllib.parse
|
|
12
18
|
from lxml.etree import fromstring, Element, tostring
|
|
13
19
|
from lxml import etree
|
|
14
20
|
from zeroconf import Zeroconf
|
|
@@ -18,7 +24,6 @@ from .oauthcli import AuthClient
|
|
|
18
24
|
from .models import *
|
|
19
25
|
from .models import UserProfile as UserProfileCurrentUser
|
|
20
26
|
from .spotifyconnect import SpotifyConnectDirectoryTask
|
|
21
|
-
from .sautils import export, GetUnixTimestampMSFromUtcNow, _xmlGetInnerText, validateDelay
|
|
22
27
|
from .saappmessages import SAAppMessages
|
|
23
28
|
from .spotifyapierror import SpotifyApiError
|
|
24
29
|
from .spotifyapimessage import SpotifyApiMessage
|
|
@@ -28,6 +33,14 @@ from .spotifywebapiauthenticationerror import SpotifyWebApiAuthenticationError
|
|
|
28
33
|
from .spotifywebapierror import SpotifyWebApiError
|
|
29
34
|
from .zeroconfapi.zeroconfconnect import ZeroconfConnect, ZeroconfGetInfo, ZeroconfResponse, SpotifyZeroconfApiError
|
|
30
35
|
from .zeroconfapi.zeroconfgetinfoalias import ZeroconfGetInfoAlias
|
|
36
|
+
from .sautils import (
|
|
37
|
+
_xmlGetInnerText,
|
|
38
|
+
export,
|
|
39
|
+
GetUnixTimestampMSFromUtcNow,
|
|
40
|
+
mediaPositionHMS_fromSeconds,
|
|
41
|
+
mediaPositionHMS_toSeconds,
|
|
42
|
+
validateDelay
|
|
43
|
+
)
|
|
31
44
|
from .const import (
|
|
32
45
|
SPOTIFY_API_AUTHORIZE_URL,
|
|
33
46
|
SPOTIFY_API_TOKEN_URL,
|
|
@@ -42,6 +55,7 @@ from .const import (
|
|
|
42
55
|
TRACE_MSG_AUTHTOKEN_CREATE,
|
|
43
56
|
TRACE_MSG_AUTOPAGING_NEXT,
|
|
44
57
|
TRACE_MSG_DELAY_DEVICE,
|
|
58
|
+
TRACE_MSG_DELAY_DEVICE_SONOS,
|
|
45
59
|
TRACE_MSG_USERPROFILE,
|
|
46
60
|
TRACE_WARN_SPOTIFY_SEARCH_NO_MARKET,
|
|
47
61
|
VERSION
|
|
@@ -733,19 +747,28 @@ class SpotifyClient:
|
|
|
733
747
|
|
|
734
748
|
def _ResolveDeviceObject(
|
|
735
749
|
self,
|
|
736
|
-
device:str | SpotifyConnectDevice
|
|
750
|
+
device:str | SpotifyConnectDevice,
|
|
751
|
+
activateDevice:bool=True,
|
|
737
752
|
) -> SpotifyConnectDevice:
|
|
738
753
|
"""
|
|
739
754
|
Resolves a SpotifyConnectDevice object from a device identifier, name, or
|
|
740
|
-
SpotifyConnectDevice object argument value.
|
|
741
|
-
if the `device` argument could not be resolved
|
|
755
|
+
SpotifyConnectDevice object argument value. A null object will be returned
|
|
756
|
+
if the `device` argument could not be resolved.
|
|
742
757
|
|
|
743
758
|
Args:
|
|
744
|
-
device (str|SpotifyConnectDevice):
|
|
745
|
-
|
|
759
|
+
device (str | SpotifyConnectDevice) | None):
|
|
760
|
+
The target player device identifier.
|
|
761
|
+
This could be an id, name, a default device indicator (e.g. "*"), a
|
|
762
|
+
SpotifyConnectDevice object, or null to utilize the active player device.
|
|
763
|
+
A device is considered resolved if a SpotifyConnectDevice object is passed
|
|
764
|
+
for this argument.
|
|
765
|
+
Examples are `0d1841b0976bae2a3a310dd74c0f3df354899bc8`, `Office`, `*`, None.
|
|
766
|
+
activateDevice (bool):
|
|
767
|
+
True to activate the device if necessary; otherwise, False.
|
|
768
|
+
Default is True.
|
|
746
769
|
|
|
747
770
|
Returns:
|
|
748
|
-
A SpotifyConnectDevice object for the specified device.
|
|
771
|
+
A SpotifyConnectDevice object for the specified device if found; otherwise, null.
|
|
749
772
|
|
|
750
773
|
Raises:
|
|
751
774
|
SpotifyApiError:
|
|
@@ -766,13 +789,13 @@ class SpotifyClient:
|
|
|
766
789
|
|
|
767
790
|
else:
|
|
768
791
|
|
|
769
|
-
# no - ensure the specified device id / name is active and
|
|
770
|
-
# and return the SpotifyConnectDevice object.
|
|
771
|
-
#
|
|
792
|
+
# no - ensure the specified device id / name is active (optional) and
|
|
793
|
+
# available, and return the SpotifyConnectDevice object.
|
|
794
|
+
# a null object will be returned if the device id could not be found.
|
|
772
795
|
scDevice = self.GetSpotifyConnectDevice(
|
|
773
796
|
device,
|
|
774
797
|
refreshDeviceList=True,
|
|
775
|
-
activateDevice=
|
|
798
|
+
activateDevice=activateDevice)
|
|
776
799
|
|
|
777
800
|
return scDevice
|
|
778
801
|
|
|
@@ -1139,12 +1162,16 @@ class SpotifyClient:
|
|
|
1139
1162
|
Example: `spotify:track:6zd8T1PBe9JFHmuVnurdRp,spotify:track:1kWUud3vY5ij5r62zxpTRy`.
|
|
1140
1163
|
An unlimited number of items can be added in one request, but the more items the longer it
|
|
1141
1164
|
will take.
|
|
1142
|
-
deviceId (str):
|
|
1143
|
-
The
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1165
|
+
deviceId (str | SpotifyConnectDevice) | None):
|
|
1166
|
+
The target player device identifier.
|
|
1167
|
+
This could be an id, name, a default device indicator (e.g. "*"), a
|
|
1168
|
+
SpotifyConnectDevice object, or null to utilize the active player device.
|
|
1169
|
+
A device is considered resolved if a SpotifyConnectDevice object is passed
|
|
1170
|
+
for this argument. An exception will be raised if the argument value could
|
|
1171
|
+
not be resolved or activated.
|
|
1172
|
+
Examples are `0d1841b0976bae2a3a310dd74c0f3df354899bc8`, `Office`, `*`, None.
|
|
1147
1173
|
verifyDeviceId (bool):
|
|
1174
|
+
DEPRECATED - deviceId is automatically verified as of v1.0.149.
|
|
1148
1175
|
True to verify a device id is active; otherwise, false to assume that a
|
|
1149
1176
|
device id is already active.
|
|
1150
1177
|
Default is True.
|
|
@@ -1166,7 +1193,7 @@ class SpotifyClient:
|
|
|
1166
1193
|
|
|
1167
1194
|
The Spotify Web API endpoint is called to add each item individually.
|
|
1168
1195
|
The Spotify Web API does not currently support adding more than 1 item to the queue at a time.
|
|
1169
|
-
|
|
1196
|
+
|
|
1170
1197
|
<details>
|
|
1171
1198
|
<summary>Sample Code</summary>
|
|
1172
1199
|
```python
|
|
@@ -1183,14 +1210,12 @@ class SpotifyClient:
|
|
|
1183
1210
|
apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
|
|
1184
1211
|
apiMethodParms.AppendKeyValue("uris", uris)
|
|
1185
1212
|
apiMethodParms.AppendKeyValue("deviceId", deviceId)
|
|
1186
|
-
apiMethodParms.AppendKeyValue("verifyDeviceId", verifyDeviceId)
|
|
1213
|
+
apiMethodParms.AppendKeyValue("verifyDeviceId (DEPRECATED)", verifyDeviceId)
|
|
1187
1214
|
apiMethodParms.AppendKeyValue("delay", delay)
|
|
1188
1215
|
_logsi.LogMethodParmList(SILevel.Verbose, "Add items to playback queue", apiMethodParms)
|
|
1189
1216
|
|
|
1190
1217
|
# validations.
|
|
1191
1218
|
delay = validateDelay(delay, 0.15, 10)
|
|
1192
|
-
if (verifyDeviceId is None):
|
|
1193
|
-
verifyDeviceId = True
|
|
1194
1219
|
|
|
1195
1220
|
# build a list of all item uri's.
|
|
1196
1221
|
# remove any leading / trailing spaces in case user put a space between the items.
|
|
@@ -1203,7 +1228,7 @@ class SpotifyClient:
|
|
|
1203
1228
|
arrUris[idx] = arrUris[idx].strip()
|
|
1204
1229
|
|
|
1205
1230
|
# resolve / activate the device object if needed.
|
|
1206
|
-
scDevice:SpotifyConnectDevice = self._ResolveDeviceObject(deviceId)
|
|
1231
|
+
scDevice:SpotifyConnectDevice = self._ResolveDeviceObject(deviceId, False)
|
|
1207
1232
|
|
|
1208
1233
|
# process all uri's.
|
|
1209
1234
|
for idx in range(0, len(arrUris)):
|
|
@@ -1213,7 +1238,8 @@ class SpotifyClient:
|
|
|
1213
1238
|
{
|
|
1214
1239
|
'uri': arrUris[idx]
|
|
1215
1240
|
}
|
|
1216
|
-
|
|
1241
|
+
if (scDevice is not None):
|
|
1242
|
+
urlParms['device_id'] = scDevice.Id
|
|
1217
1243
|
|
|
1218
1244
|
# execute spotify web api request.
|
|
1219
1245
|
msg:SpotifyApiMessage = SpotifyApiMessage(apiMethodName, '/me/player/queue')
|
|
@@ -1966,7 +1992,7 @@ class SpotifyClient:
|
|
|
1966
1992
|
A comma-separated list of Spotify User ID's to check.
|
|
1967
1993
|
Maximum: 5 ID's.
|
|
1968
1994
|
Example: `1kWUud3vY5ij5r62zxpTRy,2takcwOaAZWiXQijPHIx7B`
|
|
1969
|
-
|
|
1995
|
+
DEPRECATED - A single item list containing current user's Spotify Username; Maximum of 1 id.
|
|
1970
1996
|
|
|
1971
1997
|
Returns:
|
|
1972
1998
|
Array of boolean, containing a single boolean status that indicates
|
|
@@ -2000,7 +2026,7 @@ class SpotifyClient:
|
|
|
2000
2026
|
# trace.
|
|
2001
2027
|
apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
|
|
2002
2028
|
apiMethodParms.AppendKeyValue("playlistId", playlistId)
|
|
2003
|
-
apiMethodParms.AppendKeyValue("userIds", userIds)
|
|
2029
|
+
apiMethodParms.AppendKeyValue("userIds (DEPRECATED)", userIds)
|
|
2004
2030
|
_logsi.LogMethodParmList(SILevel.Verbose, "Check to see if users are following a playlist", apiMethodParms)
|
|
2005
2031
|
|
|
2006
2032
|
# validations.
|
|
@@ -5457,7 +5483,8 @@ class SpotifyClient:
|
|
|
5457
5483
|
Example: `sv_SE`
|
|
5458
5484
|
refresh (bool):
|
|
5459
5485
|
True to return real-time information from the spotify web api and
|
|
5460
|
-
update the cache; otherwise, False to just return the cached value.
|
|
5486
|
+
update the cache; otherwise, False to just return the cached value.
|
|
5487
|
+
Default is True.
|
|
5461
5488
|
|
|
5462
5489
|
Returns:
|
|
5463
5490
|
A `list[Category]` object that contains the list category details.
|
|
@@ -6075,6 +6102,94 @@ class SpotifyClient:
|
|
|
6075
6102
|
_logsi.LeaveMethod(SILevel.Debug, apiMethodName)
|
|
6076
6103
|
|
|
6077
6104
|
|
|
6105
|
+
def GetDevicePlaybackState(
|
|
6106
|
+
self,
|
|
6107
|
+
deviceId:str|SpotifyConnectDevice|None=None,
|
|
6108
|
+
) -> PlayerPlayState:
|
|
6109
|
+
"""
|
|
6110
|
+
Get information about the current playback state of the device, including track or episode,
|
|
6111
|
+
and progress. For Sonos devices, this will return the playstate of the device instead of the
|
|
6112
|
+
active Spotify Web API player playstate.
|
|
6113
|
+
|
|
6114
|
+
This method requires the `user-read-playback-state` scope.
|
|
6115
|
+
|
|
6116
|
+
This is not part of the Spotify Web API specification.
|
|
6117
|
+
|
|
6118
|
+
Args:
|
|
6119
|
+
deviceId (str | SpotifyConnectDevice) | None):
|
|
6120
|
+
The target player device identifier.
|
|
6121
|
+
This could be an id, name, a default device indicator (e.g. "*"), a
|
|
6122
|
+
SpotifyConnectDevice object, or null to utilize the active player device.
|
|
6123
|
+
A device is considered resolved if a SpotifyConnectDevice object is passed
|
|
6124
|
+
for this argument. An exception will be raised if the argument value could
|
|
6125
|
+
not be resolved.
|
|
6126
|
+
Examples are `0d1841b0976bae2a3a310dd74c0f3df354899bc8`, `Office`, `*`, None.
|
|
6127
|
+
|
|
6128
|
+
Returns:
|
|
6129
|
+
A `PlayerPlayState` object that contains player state details as well as
|
|
6130
|
+
currently playing content.
|
|
6131
|
+
|
|
6132
|
+
Raises:
|
|
6133
|
+
SpotifyWebApiError:
|
|
6134
|
+
If the Spotify Web API request was for a non-authorization service
|
|
6135
|
+
and the response contains error information.
|
|
6136
|
+
SpotifyApiError:
|
|
6137
|
+
If the method fails for any other reason.
|
|
6138
|
+
|
|
6139
|
+
<details>
|
|
6140
|
+
<summary>Sample Code</summary>
|
|
6141
|
+
```python
|
|
6142
|
+
.. include:: ../docs/include/samplecode/SpotifyClient/GetDevicePlaybackState.py
|
|
6143
|
+
```
|
|
6144
|
+
</details>
|
|
6145
|
+
"""
|
|
6146
|
+
apiMethodName:str = 'GetDevicePlaybackState'
|
|
6147
|
+
apiMethodParms:SIMethodParmListContext = None
|
|
6148
|
+
result:PlayerPlayState = None
|
|
6149
|
+
|
|
6150
|
+
try:
|
|
6151
|
+
|
|
6152
|
+
# trace.
|
|
6153
|
+
apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
|
|
6154
|
+
apiMethodParms.AppendKeyValue("deviceId", deviceId)
|
|
6155
|
+
_logsi.LogMethodParmList(SILevel.Verbose, "Get the current playback state of the device", apiMethodParms)
|
|
6156
|
+
|
|
6157
|
+
# resolve the device object; no need to activate it for what we want here.
|
|
6158
|
+
# this will also set the currently active device in the devices cache, as well as
|
|
6159
|
+
# add the playstate to the configuration cache.
|
|
6160
|
+
scDevice:SpotifyConnectDevice = self._ResolveDeviceObject(deviceId, activateDevice=False)
|
|
6161
|
+
|
|
6162
|
+
# get cached Spotify Web API playerstate (_ResolveDeviceObject method just refreshed it).
|
|
6163
|
+
result = self.GetPlayerPlaybackState(refresh=False)
|
|
6164
|
+
|
|
6165
|
+
# is this a Sonos device?
|
|
6166
|
+
if (scDevice is not None) and (scDevice.IsSonos):
|
|
6167
|
+
|
|
6168
|
+
# get Sonos playback state via Sonos Controller instance.
|
|
6169
|
+
playerStateSonos:PlayerPlayState = self.GetPlayerPlaybackStateSonos(scDevice)
|
|
6170
|
+
|
|
6171
|
+
# return the device playstate.
|
|
6172
|
+
_logsi.LogVerbose("Sonos device %s playstate will be returned" % (scDevice.Title))
|
|
6173
|
+
result = playerStateSonos
|
|
6174
|
+
|
|
6175
|
+
# trace.
|
|
6176
|
+
_logsi.LogObject(SILevel.Verbose, TRACE_METHOD_RESULT_TYPE % (apiMethodName, type(result).__name__), result, excludeNonPublic=True)
|
|
6177
|
+
return result
|
|
6178
|
+
|
|
6179
|
+
except SpotifyApiError: raise # pass handled exceptions on thru
|
|
6180
|
+
except SpotifyWebApiError: raise # pass handled exceptions on thru
|
|
6181
|
+
except SpotifyWebApiAuthenticationError: raise # pass handled exceptions on thru
|
|
6182
|
+
except Exception as ex:
|
|
6183
|
+
|
|
6184
|
+
# format unhandled exception.
|
|
6185
|
+
raise SpotifyApiError(SAAppMessages.UNHANDLED_EXCEPTION.format(apiMethodName, str(ex)), ex, logsi=_logsi)
|
|
6186
|
+
|
|
6187
|
+
finally:
|
|
6188
|
+
|
|
6189
|
+
# trace.
|
|
6190
|
+
_logsi.LeaveMethod(SILevel.Debug, apiMethodName)
|
|
6191
|
+
|
|
6192
|
+
|
|
6078
6193
|
def GetEpisode(
|
|
6079
6194
|
self,
|
|
6080
6195
|
episodeId:str=None,
|
|
@@ -7477,7 +7592,8 @@ class SpotifyClient:
|
|
|
7477
7592
|
def GetPlayerPlaybackState(
|
|
7478
7593
|
self,
|
|
7479
7594
|
market:str=None,
|
|
7480
|
-
additionalTypes:str=None
|
|
7595
|
+
additionalTypes:str=None,
|
|
7596
|
+
refresh:bool=True,
|
|
7481
7597
|
) -> PlayerPlayState:
|
|
7482
7598
|
"""
|
|
7483
7599
|
Get information about the user's current playback state, including track or episode, progress,
|
|
@@ -7501,6 +7617,10 @@ class SpotifyClient:
|
|
|
7501
7617
|
Note: This parameter was introduced to allow existing clients to maintain their current behaviour
|
|
7502
7618
|
and might be deprecated in the future. In addition to providing this parameter, make sure that your client
|
|
7503
7619
|
properly handles cases of new types in the future by checking against the type field of each object.
|
|
7620
|
+
refresh (bool):
|
|
7621
|
+
True to return real-time information from the Spotify Web API and
|
|
7622
|
+
update the cache; otherwise, False to just return the cached value.
|
|
7623
|
+
Default is True.
|
|
7504
7624
|
|
|
7505
7625
|
Returns:
|
|
7506
7626
|
A `PlayerPlayState` object that contains player state details as well as
|
|
@@ -7529,8 +7649,25 @@ class SpotifyClient:
|
|
|
7529
7649
|
# trace.
|
|
7530
7650
|
apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
|
|
7531
7651
|
apiMethodParms.AppendKeyValue("market", market)
|
|
7652
|
+
apiMethodParms.AppendKeyValue("additionalTypes", additionalTypes)
|
|
7653
|
+
apiMethodParms.AppendKeyValue("refresh", refresh)
|
|
7532
7654
|
_logsi.LogMethodParmList(SILevel.Verbose, "Get the user's current playback state", apiMethodParms)
|
|
7533
|
-
|
|
7655
|
+
|
|
7656
|
+
# validations.
|
|
7657
|
+
if (refresh is None):
|
|
7658
|
+
refresh = True
|
|
7659
|
+
|
|
7660
|
+
# are we getting the cached value? if so, then just return it.
|
|
7661
|
+
if (not refresh):
|
|
7662
|
+
|
|
7663
|
+
result = self._ConfigurationCache.get(apiMethodName, None)
|
|
7664
|
+
if (result is None):
|
|
7665
|
+
result = PlayerPlayState()
|
|
7666
|
+
|
|
7667
|
+
# trace.
|
|
7668
|
+
_logsi.LogObject(SILevel.Verbose, TRACE_METHOD_RESULT_TYPE % (apiMethodName + " (cached)", type(result).__name__), result, excludeNonPublic=True)
|
|
7669
|
+
return result
|
|
7670
|
+
|
|
7534
7671
|
# ensure market was either supplied or implied; default if neither.
|
|
7535
7672
|
market = self._ValidateMarket(market)
|
|
7536
7673
|
|
|
@@ -7549,9 +7686,12 @@ class SpotifyClient:
|
|
|
7549
7686
|
|
|
7550
7687
|
# process results.
|
|
7551
7688
|
result = PlayerPlayState(root=msg.ResponseData)
|
|
7552
|
-
|
|
7689
|
+
|
|
7690
|
+
# update cache with result.
|
|
7691
|
+
self._ConfigurationCache[apiMethodName] = result
|
|
7692
|
+
|
|
7553
7693
|
# is this an episode? if so, then determine the item type (e.g. podcast or audiobook).
|
|
7554
|
-
if (
|
|
7694
|
+
if (not result.IsEmpty):
|
|
7555
7695
|
if (result.Item is not None):
|
|
7556
7696
|
result.ItemType = SpotifyClient.GetTypeFromUri(result.Item.Uri)
|
|
7557
7697
|
if (result.CurrentlyPlayingType == 'episode'):
|
|
@@ -7563,7 +7703,7 @@ class SpotifyClient:
|
|
|
7563
7703
|
|
|
7564
7704
|
# check for a restricted device; if found, then grab the device id from the
|
|
7565
7705
|
# cached list of spotify connect devices (if present).
|
|
7566
|
-
if
|
|
7706
|
+
if (not result.IsEmpty):
|
|
7567
7707
|
device:Device = result.Device
|
|
7568
7708
|
if (device is not None) and (device.Id is None or device.Id == ''):
|
|
7569
7709
|
cacheKey:str = 'GetSpotifyConnectDevices'
|
|
@@ -7573,6 +7713,11 @@ class SpotifyClient:
|
|
|
7573
7713
|
if scDevice is not None:
|
|
7574
7714
|
device.Id = scDevice.DeviceInfo.DeviceId
|
|
7575
7715
|
|
|
7716
|
+
# update Spotify Connect Directory with active device details.
|
|
7717
|
+
# IMPORTANT - make sure result contains an object, otherwise it's an endless loop!
|
|
7718
|
+
if (result is not None):
|
|
7719
|
+
self.SpotifyConnectDirectory.UpdateActiveDevice(result)
|
|
7720
|
+
|
|
7576
7721
|
# trace.
|
|
7577
7722
|
_logsi.LogObject(SILevel.Verbose, TRACE_METHOD_RESULT_TYPE % (apiMethodName, type(result).__name__), result, excludeNonPublic=True)
|
|
7578
7723
|
return result
|
|
@@ -7591,6 +7736,222 @@ class SpotifyClient:
|
|
|
7591
7736
|
_logsi.LeaveMethod(SILevel.Debug, apiMethodName)
|
|
7592
7737
|
|
|
7593
7738
|
|
|
7739
|
+
def GetPlayerPlaybackStateSonos(
|
|
7740
|
+
self,
|
|
7741
|
+
scDevice:SpotifyConnectDevice,
|
|
7742
|
+
) -> PlayerPlayState:
|
|
7743
|
+
"""
|
|
7744
|
+
Get information about the Sonos user's current playback state, including track or episode, progress,
|
|
7745
|
+
and active device.
|
|
7746
|
+
|
|
7747
|
+
This is not part of the Spotify Web API specification; it is for Sonos devices only!
|
|
7748
|
+
|
|
7749
|
+
Args:
|
|
7750
|
+
scDevice (SpotifyConnectDevice):
|
|
7751
|
+
Spotify Connect device instance used to identify the Sonos player.
|
|
7752
|
+
|
|
7753
|
+
Returns:
|
|
7754
|
+
A `PlayerPlayState` object that contains Sonos player state details as well as
|
|
7755
|
+
currently playing content.
|
|
7756
|
+
|
|
7757
|
+
Raises:
|
|
7758
|
+
SpotifyApiError:
|
|
7759
|
+
If the method fails for any other reason.
|
|
7760
|
+
"""
|
|
7761
|
+
apiMethodName:str = 'GetPlayerPlaybackStateSonos'
|
|
7762
|
+
apiMethodParms:SIMethodParmListContext = None
|
|
7763
|
+
|
|
7764
|
+
try:
|
|
7765
|
+
|
|
7766
|
+
# trace.
|
|
7767
|
+
apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
|
|
7768
|
+
apiMethodParms.AppendKeyValue("scDevice", scDevice)
|
|
7769
|
+
_logsi.LogMethodParmList(SILevel.Verbose, "Get Sonos device current playback state", apiMethodParms)
|
|
7770
|
+
|
|
7771
|
+
# create playstate object.
|
|
7772
|
+
playerState = PlayerPlayState()
|
|
7773
|
+
|
|
7774
|
+
# get Sonos Controller instance for the device.
|
|
7775
|
+
_logsi.LogVerbose("Getting Sonos device status via Sonos Controller instance for device: %s" % (scDevice.Title))
|
|
7776
|
+
sonosPlayer:SoCo = self.SpotifyConnectDirectory.GetSonosPlayer(scDevice)
|
|
7777
|
+
|
|
7778
|
+
# build a spotify playback status instance with equivalent Sonos state values.
|
|
7779
|
+
sonosTrackInfo:dict = sonosPlayer.get_current_track_info()
|
|
7780
|
+
_logsi.LogDictionary(SILevel.Verbose, "Sonos device current_track_info results for device: %s" % (scDevice.Title), sonosTrackInfo, prettyPrint=True)
|
|
7781
|
+
sonosTransportInfo:dict = sonosPlayer.get_current_transport_info()
|
|
7782
|
+
_logsi.LogDictionary(SILevel.Verbose, "Sonos device get_current_transport_info results for device: %s" % (scDevice.Title), sonosTransportInfo, prettyPrint=True)
|
|
7783
|
+
|
|
7784
|
+
# indicate this is a device-specific playstate.
|
|
7785
|
+
playerState.IsDeviceState = True
|
|
7786
|
+
playerState.DeviceMusicSource = "" + str(sonosPlayer.music_source)
|
|
7787
|
+
|
|
7788
|
+
# update Spotify Web API player state with Sonos player state.
|
|
7789
|
+
playerState._Device._Id = scDevice.Id
|
|
7790
|
+
playerState._Device._Name = sonosPlayer.player_name
|
|
7791
|
+
playerState._Device._VolumePercent = sonosPlayer.volume
|
|
7792
|
+
playerState._Device._IsActive = True
|
|
7793
|
+
playerState._Device._IsRestricted = True
|
|
7794
|
+
playerState._Device._SupportsVolume = True
|
|
7795
|
+
playerState._Device._Type = 'SPEAKER'
|
|
7796
|
+
|
|
7797
|
+
# try to get the spotify uri from the "metadata" music library DIDL value, as
|
|
7798
|
+
# the "uri" value (e.g. "x-sonos-vli:RINCON_38420B909DC801400:2,spotify:e934941535d7b182815bf688490ce8a8")
|
|
7799
|
+
# is not a true spotify uri value (e.g. "spotify:track:6kYyS9g4WJeRzTYqsmcMmM")
|
|
7800
|
+
spotifyUri:str = sonosTrackInfo.get('uri','')
|
|
7801
|
+
didl:str = sonosTrackInfo.get('metadata',None)
|
|
7802
|
+
if (didl):
|
|
7803
|
+
METADATA_ID:str = 'x-sonos-spotify:'
|
|
7804
|
+
idx:int = didl.find(METADATA_ID)
|
|
7805
|
+
if (idx > -1):
|
|
7806
|
+
spotifyUri:str = didl[idx + len(METADATA_ID):]
|
|
7807
|
+
spotifyUri = urllib.parse.unquote(spotifyUri)
|
|
7808
|
+
idx = spotifyUri.find('?')
|
|
7809
|
+
if (idx > -1):
|
|
7810
|
+
spotifyUri = spotifyUri[:idx]
|
|
7811
|
+
spotifyType = self.GetTypeFromUri(spotifyUri) or ''
|
|
7812
|
+
spotifyId = self.GetIdFromUri(spotifyUri) or ''
|
|
7813
|
+
|
|
7814
|
+
# set base item properties.
|
|
7815
|
+
playerState.IsEmpty = False
|
|
7816
|
+
playerState.ItemType = spotifyType
|
|
7817
|
+
sTimeValue:str = sonosTrackInfo.get('position',None)
|
|
7818
|
+
playerState._ProgressMS = mediaPositionHMS_toSeconds(sTimeValue) * 1000 # convert h:mm:ss to milliseconds
|
|
7819
|
+
|
|
7820
|
+
# set some item properties based on the playing type (episode or non-episode).
|
|
7821
|
+
if (spotifyType == 'episode'):
|
|
7822
|
+
|
|
7823
|
+
# get the episode data from spotify, as Sonos Soco data is incomplete.
|
|
7824
|
+
playerState._CurrentlyPlayingType = 'episode'
|
|
7825
|
+
episode:Episode = self.GetEpisode(spotifyId)
|
|
7826
|
+
if (episode.Id is not None):
|
|
7827
|
+
playerState._Item = episode
|
|
7828
|
+
if (playerState._Context is None):
|
|
7829
|
+
playerState._Context = Context()
|
|
7830
|
+
playerState._Context._Type = episode.Show.Type
|
|
7831
|
+
playerState._Context._Uri = episode.Show.Uri
|
|
7832
|
+
playerState._Context._Href = episode.Show.Href
|
|
7833
|
+
playerState._Context._ExternalUrls = episode.Show.ExternalUrls
|
|
7834
|
+
else:
|
|
7835
|
+
# if data could not be obtained from spotify, then use what's
|
|
7836
|
+
# available from Sonos Soco api metadata.
|
|
7837
|
+
playerState._Item = Episode()
|
|
7838
|
+
playerState._Item._Name = sonosTrackInfo.get('title','')
|
|
7839
|
+
playerState._Item._Uri = spotifyUri
|
|
7840
|
+
playerState._Item._Description = 'Sonos device does not provide a description'
|
|
7841
|
+
sTimeValue:str = sonosTrackInfo.get('duration',None)
|
|
7842
|
+
playerState._Item._DurationMS = mediaPositionHMS_toSeconds(sTimeValue) * 1000 # convert h:mm:ss to milliseconds
|
|
7843
|
+
playerState._Item._Explicit = False
|
|
7844
|
+
playerState._Item._Href = 'https://api.spotify.com/v1/episodes/' + spotifyId
|
|
7845
|
+
playerState._Item._HtmlDescription = playerState._Item._Description
|
|
7846
|
+
playerState._Item._Id = spotifyId
|
|
7847
|
+
playerState._Item.Images.append(ImageObject())
|
|
7848
|
+
playerState._Item.Images[0]._Url = sonosTrackInfo.get('album_art','')
|
|
7849
|
+
playerState._Item._TrackNumber = sonosTrackInfo.get('playlist_position','')
|
|
7850
|
+
playerState._Item._ReleaseDate = '0000'
|
|
7851
|
+
playerState._Item._ReleaseDatePrecision = 'year'
|
|
7852
|
+
playerState._Item._Type = 'episode'
|
|
7853
|
+
playerState._Item._Show = Show()
|
|
7854
|
+
playerState._Item._Show._Name = sonosTrackInfo.get('album','') # TODO get this from metadata (<r:podcast>The Elfstones of Shannara</r:podcast>)
|
|
7855
|
+
playerState._Item._Show.Images.append(ImageObject())
|
|
7856
|
+
playerState._Item._Show.Images[0]._Url = sonosTrackInfo.get('album_art','')
|
|
7857
|
+
playerState._Item._Show._Publisher = sonosTrackInfo.get('artist','')
|
|
7858
|
+
|
|
7859
|
+
# if show name not set then get it from metadata.
|
|
7860
|
+
if (playerState._Item._Show._Name == ''):
|
|
7861
|
+
if (didl):
|
|
7862
|
+
METADATA_ID:str = '<r:podcast>'
|
|
7863
|
+
METADATA_END:str = '</r:podcast>'
|
|
7864
|
+
idx:int = didl.find(METADATA_ID)
|
|
7865
|
+
if (idx > -1):
|
|
7866
|
+
metaValue:str = didl[idx + len(METADATA_ID):]
|
|
7867
|
+
idx = metaValue.find(METADATA_END)
|
|
7868
|
+
if (idx > -1):
|
|
7869
|
+
metaValue = metaValue[:idx]
|
|
7870
|
+
playerState._Item._Show._Name = metaValue
|
|
7871
|
+
|
|
7872
|
+
# if episode is playing then resolve the underlying type (audiobook / podcast show).
|
|
7873
|
+
if (self.IsChapterEpisode(spotifyId)):
|
|
7874
|
+
playerState.ItemType = 'audiobook'
|
|
7875
|
+
else:
|
|
7876
|
+
playerState.ItemType = 'podcast'
|
|
7877
|
+
|
|
7878
|
+
elif (spotifyType == 'track'):
|
|
7879
|
+
|
|
7880
|
+
# get the track data from spotify, as Sonos Soco data is incomplete.
|
|
7881
|
+
track:Track = self.GetTrack(spotifyId)
|
|
7882
|
+
if (track.Id is not None):
|
|
7883
|
+
playerState._Item = track
|
|
7884
|
+
if (playerState._Context is None):
|
|
7885
|
+
playerState._Context = Context()
|
|
7886
|
+
playerState._Context._Type = track.Album.Type
|
|
7887
|
+
playerState._Context._Uri = track.Album.Uri
|
|
7888
|
+
playerState._Context._Href = track.Album.Href
|
|
7889
|
+
playerState._Context._ExternalUrls = track.Album.ExternalUrls
|
|
7890
|
+
else:
|
|
7891
|
+
# if data could not be obtained from spotify, then use what's
|
|
7892
|
+
# available from Sonos Soco api metadata.
|
|
7893
|
+
playerState._CurrentlyPlayingType = 'track'
|
|
7894
|
+
playerState.ItemType = playerState._CurrentlyPlayingType
|
|
7895
|
+
playerState._Item = Track()
|
|
7896
|
+
playerState._Item._Name = sonosTrackInfo.get('title','')
|
|
7897
|
+
playerState._Item._Uri = spotifyUri
|
|
7898
|
+
playerState._Item._Description = 'Sonos device does not provide a description'
|
|
7899
|
+
sTimeValue:str = sonosTrackInfo.get('duration',None)
|
|
7900
|
+
playerState._Item._DurationMS = mediaPositionHMS_toSeconds(sTimeValue) * 1000 # convert h:mm:ss to milliseconds
|
|
7901
|
+
playerState._Item._Explicit = False
|
|
7902
|
+
playerState._Item._Href = 'https://api.spotify.com/v1/episodes/' + spotifyId
|
|
7903
|
+
playerState._Item._HtmlDescription = playerState._Item._Description
|
|
7904
|
+
playerState._Item._Id = spotifyId
|
|
7905
|
+
playerState._Item._TrackNumber = sonosTrackInfo.get('playlist_position','')
|
|
7906
|
+
playerState._Item._ReleaseDate = '0000'
|
|
7907
|
+
playerState._Item._ReleaseDatePrecision = 'year'
|
|
7908
|
+
playerState._Item._Type = 'track'
|
|
7909
|
+
playerState._Item._Album = Album()
|
|
7910
|
+
playerState._Item._Album._Name = sonosTrackInfo.get('album','')
|
|
7911
|
+
playerState._Item._Album.Images.append(ImageObject())
|
|
7912
|
+
playerState._Item._Album.Images[0]._Url = sonosTrackInfo.get('album_art','')
|
|
7913
|
+
playerState._Item.Artists.append(Artist())
|
|
7914
|
+
playerState._Item.Artists[0]._Name = sonosTrackInfo.get('artist','')
|
|
7915
|
+
|
|
7916
|
+
# set transport actions.
|
|
7917
|
+
currentTransportState:str = sonosTransportInfo.get('current_transport_state','')
|
|
7918
|
+
if currentTransportState == 'PLAYING':
|
|
7919
|
+
playerState._IsPlaying = True
|
|
7920
|
+
elif currentTransportState in ['PAUSED_PLAYBACK','STOPPED']:
|
|
7921
|
+
playerState.Actions._Pausing = True
|
|
7922
|
+
|
|
7923
|
+
# only update the following PlayState attributes from SoCo if they are NOT set.
|
|
7924
|
+
# these values are used to set the Sonos PlayMode value, which is the actual value that determines
|
|
7925
|
+
# what the Repeat and Shuffle settings are (ya, it's weird!). What is odd is that the Spotify
|
|
7926
|
+
# Web API PlayerState values for Shuffle and Repeat can be different than what the Sonos Controller
|
|
7927
|
+
# API (SoCo) report for the device! This is why we use the Spotify Web API PlayerState values.
|
|
7928
|
+
if playerState._ShuffleState is None:
|
|
7929
|
+
playerState._ShuffleState = sonosPlayer.shuffle
|
|
7930
|
+
if playerState._RepeatState is None:
|
|
7931
|
+
sonosRepeat = sonosPlayer.repeat
|
|
7932
|
+
if sonosRepeat == 'ONE':
|
|
7933
|
+
playerState._RepeatState = 'track'
|
|
7934
|
+
elif sonosRepeat == False:
|
|
7935
|
+
playerState._RepeatState = 'off'
|
|
7936
|
+
elif sonosRepeat == True:
|
|
7937
|
+
playerState._RepeatState = 'context'
|
|
7938
|
+
|
|
7939
|
+
# trace.
|
|
7940
|
+
_logsi.LogObject(SILevel.Verbose, "Current Spotify player PlayState (built from Sonos device current state) for device: %s" % (scDevice.Title), playerState, excludeNonPublic=True)
|
|
7941
|
+
return playerState
|
|
7942
|
+
|
|
7943
|
+
except SpotifyApiError: raise # pass handled exceptions on thru
|
|
7944
|
+
except Exception as ex:
|
|
7945
|
+
|
|
7946
|
+
# format unhandled exception.
|
|
7947
|
+
raise SpotifyApiError(SAAppMessages.UNHANDLED_EXCEPTION.format(apiMethodName, str(ex)), ex, logsi=_logsi)
|
|
7948
|
+
|
|
7949
|
+
finally:
|
|
7950
|
+
|
|
7951
|
+
# trace.
|
|
7952
|
+
_logsi.LeaveMethod(SILevel.Debug, apiMethodName)
|
|
7953
|
+
|
|
7954
|
+
|
|
7594
7955
|
def GetPlayerQueueInfo(self) -> PlayerQueueInfo:
|
|
7595
7956
|
"""
|
|
7596
7957
|
Get the list of objects that make up the user's playback queue.
|
|
@@ -9161,7 +9522,7 @@ class SpotifyClient:
|
|
|
9161
9522
|
def GetSpotifyConnectDevice(
|
|
9162
9523
|
self,
|
|
9163
9524
|
deviceValue:str,
|
|
9164
|
-
verifyUserContext:bool=True,
|
|
9525
|
+
verifyUserContext:bool=True,
|
|
9165
9526
|
verifyTimeout:float=5.0,
|
|
9166
9527
|
refreshDeviceList:bool=False,
|
|
9167
9528
|
activateDevice:bool=True,
|
|
@@ -9207,7 +9568,7 @@ class SpotifyClient:
|
|
|
9207
9568
|
Returns:
|
|
9208
9569
|
A `SpotifyConnectDevice` object for the deviceValue if one could be resolved.
|
|
9209
9570
|
otherwise, a null value with the understanding that subsequent operations will probably
|
|
9210
|
-
fail since it's not in the Spotify Connect
|
|
9571
|
+
fail since it's not in the Spotify Connect device list.
|
|
9211
9572
|
|
|
9212
9573
|
Raises:
|
|
9213
9574
|
SpotifyWebApiError:
|
|
@@ -9244,13 +9605,14 @@ class SpotifyClient:
|
|
|
9244
9605
|
apiMethodName:str = 'GetSpotifyConnectDevice'
|
|
9245
9606
|
apiMethodParms:SIMethodParmListContext = None
|
|
9246
9607
|
scDevice:SpotifyConnectDevice = None
|
|
9608
|
+
sonosPlayer:SoCo = None
|
|
9247
9609
|
|
|
9248
9610
|
try:
|
|
9249
9611
|
|
|
9250
9612
|
# trace.
|
|
9251
9613
|
apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
|
|
9252
9614
|
apiMethodParms.AppendKeyValue("deviceValue", deviceValue)
|
|
9253
|
-
apiMethodParms.AppendKeyValue("verifyUserContext", verifyUserContext)
|
|
9615
|
+
apiMethodParms.AppendKeyValue("verifyUserContext (DEPRECATED)", verifyUserContext)
|
|
9254
9616
|
apiMethodParms.AppendKeyValue("verifyTimeout", verifyTimeout)
|
|
9255
9617
|
apiMethodParms.AppendKeyValue("refreshDeviceList", refreshDeviceList)
|
|
9256
9618
|
apiMethodParms.AppendKeyValue("activateDevice", activateDevice)
|
|
@@ -9266,8 +9628,15 @@ class SpotifyClient:
|
|
|
9266
9628
|
refreshDeviceList = False
|
|
9267
9629
|
|
|
9268
9630
|
# resolve the device id from the specified device value.
|
|
9269
|
-
#
|
|
9270
|
-
scDevice = self._SpotifyConnectDirectory.GetDevice(deviceValue, refreshDynamicDevices=True)
|
|
9631
|
+
# returned value will be null if the device could not be resolved!
|
|
9632
|
+
scDevice = self._SpotifyConnectDirectory.GetDevice(deviceValue, refreshDynamicDevices=True, raiseExceptionIfNotFound=False)
|
|
9633
|
+
|
|
9634
|
+
# if device could not be resolved, then go no further as it will result in a
|
|
9635
|
+
# "device not found" error from the Spotify Web API anyway!
|
|
9636
|
+
if (scDevice is None):
|
|
9637
|
+
_logsi.LogVerbose("Spotify Player device was not found, and there is no active Spotify player; no need to re-activate")
|
|
9638
|
+
return None
|
|
9639
|
+
|
|
9271
9640
|
discovery:ZeroconfDiscoveryResult = scDevice.DiscoveryResult
|
|
9272
9641
|
info:ZeroconfGetInfo = scDevice.DeviceInfo
|
|
9273
9642
|
scDevice.WasReConnected = False
|
|
@@ -9277,32 +9646,17 @@ class SpotifyClient:
|
|
|
9277
9646
|
_logsi.LogVerbose("Spotify Connect device %s is already active; no need to re-activate" % (scDevice.Title))
|
|
9278
9647
|
return scDevice
|
|
9279
9648
|
|
|
9280
|
-
# is the device in the player device list? if so, then it's
|
|
9281
|
-
# if another user is controlling the device, then it will not be in the
|
|
9282
|
-
# for the current user.
|
|
9649
|
+
# is the device in the player device list? if so, then it's ready to be selected.
|
|
9650
|
+
# note that if another user is controlling the device, then it will not be in the
|
|
9651
|
+
# active device list for the current user.
|
|
9283
9652
|
if (scDevice.IsInDeviceList):
|
|
9284
9653
|
_logsi.LogVerbose("Spotify Connect device %s is already in the available device list; no need to re-activate" % (scDevice.Title))
|
|
9285
9654
|
return scDevice
|
|
9286
9655
|
|
|
9287
|
-
# chromecast devices always require activation if they are not the active device.
|
|
9288
|
-
# note that `transferPlayback=false`, as transfer should be done after we return to caller.
|
|
9289
|
-
if (scDevice.IsChromeCast):
|
|
9290
|
-
|
|
9291
|
-
# activate the spotify cast application on the device.
|
|
9292
|
-
_logsi.LogVerbose("Activating Chromecast Spotify Connect device: %s" % (scDevice.Title))
|
|
9293
|
-
self._SpotifyConnectDirectory.ActivateCastAppSpotify(scDevice.Name, transferPlayback=False)
|
|
9294
|
-
|
|
9295
|
-
# re-fetch device instance, as it has updated Zeroconf Response data.
|
|
9296
|
-
# do not need to refresh from Spotify Web API, as only zeroconf data was changed.
|
|
9297
|
-
scDevice = self._SpotifyConnectDirectory.GetDevice(deviceValue, refreshDynamicDevices=False)
|
|
9298
|
-
|
|
9299
|
-
# return device to caller.
|
|
9300
|
-
return scDevice
|
|
9301
|
-
|
|
9302
9656
|
# if this is a Sonos device and there is no Spotify Client Application token then don't bother.
|
|
9303
9657
|
# in this case, we will exit and let the calling method switch to the Sonos device and play
|
|
9304
9658
|
# using the Sonos local queue.
|
|
9305
|
-
if (
|
|
9659
|
+
if (scDevice.IsSonos):
|
|
9306
9660
|
|
|
9307
9661
|
# check if the Spotify Desktop Application Client oauth2 token is defined.
|
|
9308
9662
|
hasToken:bool = AuthClient.HasTokenForKey(
|
|
@@ -9318,13 +9672,32 @@ class SpotifyClient:
|
|
|
9318
9672
|
_logsi.LogVerbose("Spotify Desktop Application Client oauth2 token was not found for Spotify LoginId: '%s'" % (self._SpotifyConnectLoginId))
|
|
9319
9673
|
return scDevice
|
|
9320
9674
|
|
|
9321
|
-
|
|
9322
|
-
|
|
9675
|
+
# get Sonos Controller instance for the device.
|
|
9676
|
+
_logsi.LogVerbose("Target device is Sonos; getting Sonos Controller instance for device %s" % (scDevice.Title))
|
|
9677
|
+
sonosPlayer = self.SpotifyConnectDirectory.GetSonosPlayer(scDevice)
|
|
9678
|
+
|
|
9323
9679
|
# was device activation requested? if not, then we are done.
|
|
9324
9680
|
if (not activateDevice):
|
|
9325
9681
|
_logsi.LogVerbose("Activation not requested for Spotify Connect device: %s" % (scDevice.Title))
|
|
9326
9682
|
return scDevice
|
|
9327
9683
|
|
|
9684
|
+
# at this point, we will try to activate the device if needed.
|
|
9685
|
+
|
|
9686
|
+
# chromecast devices always require activation if they are not the active device.
|
|
9687
|
+
# note that `transferPlayback=false`, as transfer should be done after we return to caller.
|
|
9688
|
+
if (scDevice.IsChromeCast):
|
|
9689
|
+
|
|
9690
|
+
# activate the spotify cast application on the device.
|
|
9691
|
+
_logsi.LogVerbose("Activating Chromecast Spotify Connect device: %s" % (scDevice.Title))
|
|
9692
|
+
self._SpotifyConnectDirectory.ActivateCastAppSpotify(scDevice.Name, transferPlayback=False)
|
|
9693
|
+
|
|
9694
|
+
# re-fetch device instance, as it has updated Zeroconf Response data.
|
|
9695
|
+
# do not need to refresh from Spotify Web API, as only zeroconf data was changed.
|
|
9696
|
+
scDevice = self._SpotifyConnectDirectory.GetDevice(deviceValue, refreshDynamicDevices=False)
|
|
9697
|
+
|
|
9698
|
+
# return device to caller.
|
|
9699
|
+
return scDevice
|
|
9700
|
+
|
|
9328
9701
|
zcfResult:ZeroconfResponse
|
|
9329
9702
|
|
|
9330
9703
|
# store the currently active user of the device, in case we need to switch users later on.
|
|
@@ -9342,87 +9715,20 @@ class SpotifyClient:
|
|
|
9342
9715
|
tokenStorageDir=self.TokenStorageDir,
|
|
9343
9716
|
tokenStorageFile=self.TokenStorageFile)
|
|
9344
9717
|
|
|
9345
|
-
#
|
|
9346
|
-
|
|
9347
|
-
|
|
9348
|
-
|
|
9349
|
-
|
|
9350
|
-
|
|
9351
|
-
|
|
9352
|
-
|
|
9353
|
-
|
|
9354
|
-
|
|
9355
|
-
|
|
9356
|
-
|
|
9357
|
-
|
|
9358
|
-
# # some devices only support a single connection on the spotify connect
|
|
9359
|
-
# # zeroconf webserver, so we have to give it a little time to process the
|
|
9360
|
-
# # disconnect command before we try to call the Connect method.
|
|
9361
|
-
# _logsi.LogVerbose(TRACE_MSG_DELAY_DEVICE % DELAY_DISCONNECT)
|
|
9362
|
-
# time.sleep(DELAY_DISCONNECT)
|
|
9363
|
-
|
|
9364
|
-
# # some manufacturers will reset the device host ip port after a disconnect (e.g. "Denon", etc).
|
|
9365
|
-
# # due to this, the device will de-register itself via zeroconf and then re-register itself.
|
|
9366
|
-
# # we will now wait for the device to be re-discovered by zeroconf.
|
|
9367
|
-
# loopTotalDelay:float = 0
|
|
9368
|
-
# LOOP_DELAY:float = 0.25
|
|
9369
|
-
# while True:
|
|
9370
|
-
|
|
9371
|
-
# # wait just a bit between available device list queries.
|
|
9372
|
-
# _logsi.LogVerbose(TRACE_MSG_DELAY_DEVICE % LOOP_DELAY)
|
|
9373
|
-
# time.sleep(LOOP_DELAY)
|
|
9374
|
-
# loopTotalDelay = loopTotalDelay + LOOP_DELAY
|
|
9375
|
-
|
|
9376
|
-
# with self._SpotifyConnectDirectory._Zeroconf_Lock:
|
|
9377
|
-
|
|
9378
|
-
# # check for the device in the available device list.
|
|
9379
|
-
# # if we find it, then we are done.
|
|
9380
|
-
|
|
9381
|
-
# device:Device = self.GetPlayerDevice(deviceResultId, True)
|
|
9382
|
-
# if device is not None:
|
|
9383
|
-
# _logsi.LogVerbose("Spotify Connect device \"%s\" is now in the available device list; device found within %f seconds of Connect" % (scDevice.Title, loopTotalDelay))
|
|
9384
|
-
# break
|
|
9385
|
-
|
|
9718
|
+
# is this a Sonos device?
|
|
9719
|
+
if (scDevice.IsSonos):
|
|
9720
|
+
|
|
9721
|
+
# trace.
|
|
9722
|
+
sonosMusicSource:str = sonosPlayer.music_source
|
|
9723
|
+
_logsi.LogVerbose("Sonos device %s music source before Connect: \"%s\"" % (scDevice.Title, sonosMusicSource))
|
|
9724
|
+
|
|
9725
|
+
# was the Sonos device music source set to SPOTIFY_CONNECT?
|
|
9726
|
+
# if not, then issue a Disconnect to (hopefully) reset the music source. this should allow
|
|
9727
|
+
# the subsequent Connect to re-establish a SPOTIFY_CONNECT music source on the Sonos device.
|
|
9728
|
+
if sonosMusicSource != "SPOTIFY_CONNECT":
|
|
9729
|
+
_logsi.LogVerbose("Issuing Disconnect to Sonos Spotify Connect device %s" % (scDevice.Title))
|
|
9730
|
+
zcfResult = zconn.Disconnect(ignoreStatusResult=True)
|
|
9386
9731
|
|
|
9387
|
-
# # re-discover Spotify Connect devices on the network
|
|
9388
|
-
# _logsi.LogVerbose("Rediscovering Spotify Connect devices on the local network (due to previous disconnect)")
|
|
9389
|
-
# rediscovery:SpotifyDiscovery = SpotifyDiscovery(self._ZeroconfClient, printToConsole=False)
|
|
9390
|
-
# rediscovery.DiscoverDevices(timeout=self._SpotifyConnectDiscoveryTimeout)
|
|
9391
|
-
|
|
9392
|
-
# # trace.
|
|
9393
|
-
# _logsi.LogDictionary(SILevel.Verbose, "Rediscovered Spotify Connect devices on the local network (due to previous disconnect)", rediscovery.DiscoveryResults, prettyPrint=True)
|
|
9394
|
-
|
|
9395
|
-
# # process all rediscovered devices, and update selected device.
|
|
9396
|
-
# rediscovery:ZeroconfDiscoveryResult
|
|
9397
|
-
# for rediscovery in rediscovery.DiscoveryResults:
|
|
9398
|
-
|
|
9399
|
-
# # add / update selected device discovery results by serviceinfo key value.
|
|
9400
|
-
# if (rediscovery.Key == scDevice.DiscoveryResult.Key):
|
|
9401
|
-
|
|
9402
|
-
# # save old discovery result id so we can log it later.
|
|
9403
|
-
# oldDiscoveryResultId:str = discovery.Id
|
|
9404
|
-
|
|
9405
|
-
# # update selected device discovery result.
|
|
9406
|
-
# _logsi.LogObject(SILevel.Verbose, "Spotify Connect device \"%s\" DiscoveryResult instance rediscovered properties" % (rediscovery.Id), rediscovery, excludeNonPublic=True)
|
|
9407
|
-
# _logsi.LogVerbose("Updating Spotify Connect device \"%s\" DiscoveryResult instance with rediscovered properties" % (scDevice.Title))
|
|
9408
|
-
# scDevice.DiscoveryResult = rediscovery
|
|
9409
|
-
# discovery = rediscovery
|
|
9410
|
-
|
|
9411
|
-
# # did the host ip address or port change?
|
|
9412
|
-
# # if so, then we need to recreate the ZeroconfConnect instance
|
|
9413
|
-
# # in order to access the updated ip address and port.
|
|
9414
|
-
# if (discovery.HostIpAddress != zconn.HostIpAddress) \
|
|
9415
|
-
# or (discovery.HostIpPort != zconn.HostIpPort):
|
|
9416
|
-
|
|
9417
|
-
# _logsi.LogVerbose("Spotify Connect device info changed to %s from %s after disconnect; recreating ZeroconfConnect instance" % (discovery.Id, oldDiscoveryResultId))
|
|
9418
|
-
# zconn = ZeroconfConnect(discovery.HostIpAddress,
|
|
9419
|
-
# discovery.HostIpPort,
|
|
9420
|
-
# discovery.SpotifyConnectCPath,
|
|
9421
|
-
# useSSL=False,
|
|
9422
|
-
# tokenStorageDir=self.TokenStorageDir,
|
|
9423
|
-
# tokenStorageFile=self.TokenStorageFile)
|
|
9424
|
-
# break
|
|
9425
|
-
|
|
9426
9732
|
# connect the device to OUR Spotify Connect user context.
|
|
9427
9733
|
# note that the result here only indicates that the connect was submitted - NOT that it was successful!
|
|
9428
9734
|
_logsi.LogVerbose("Issuing Connect to Spotify Connect device %s for user context \"%s\" (ip=%s:%s)" % (scDevice.Title, self._SpotifyConnectLoginId, zconn.HostIpAddress, zconn.HostIpPort))
|
|
@@ -9434,6 +9740,26 @@ class SpotifyClient:
|
|
|
9434
9740
|
# trace.
|
|
9435
9741
|
_logsi.LogVerbose("User context switched from \"%s\" to \"%s\" for Spotify Connect device %s" % (oldActiveUser, self._SpotifyConnectLoginId, scDevice.Title))
|
|
9436
9742
|
|
|
9743
|
+
# is this a Sonos device?
|
|
9744
|
+
if (scDevice.IsSonos):
|
|
9745
|
+
|
|
9746
|
+
# trace.
|
|
9747
|
+
sonosMusicSource:str = sonosPlayer.music_source
|
|
9748
|
+
_logsi.LogVerbose("Sonos device %s music source after Connect: \"%s\"" % (scDevice.Title, sonosMusicSource))
|
|
9749
|
+
|
|
9750
|
+
# was the Sonos device music source set to Spotify Connect?
|
|
9751
|
+
# if not, then it's a lost cause at this point since it's probably in an UNKNOWN state.
|
|
9752
|
+
if sonosMusicSource != "SPOTIFY_CONNECT":
|
|
9753
|
+
_logsi.LogVerbose("Sonos device %s music source after Connect is not SPOTIFY_CONNECT; it will probably fail to play" % (scDevice.Title))
|
|
9754
|
+
|
|
9755
|
+
# at this point the Sonos music source should be set to SPOTIFY_CONNECT, and control
|
|
9756
|
+
# transferred to the device. note that the Sonos device will still NOT appear in the
|
|
9757
|
+
# player device list, but SHOULD be the active (restricted) device in player state.
|
|
9758
|
+
|
|
9759
|
+
# wait for the device to enter the Spotify Web API player device list, or
|
|
9760
|
+
# to become the active device.
|
|
9761
|
+
# Sonos devices are famous for not showing up in the device list, but DO
|
|
9762
|
+
# appear as the active (restricted) device!
|
|
9437
9763
|
loopTotalDelay:float = 0
|
|
9438
9764
|
LOOP_DELAY:float = 0.350
|
|
9439
9765
|
while True:
|
|
@@ -9466,7 +9792,7 @@ class SpotifyClient:
|
|
|
9466
9792
|
# only check so many times before we give up;
|
|
9467
9793
|
# we will keep checking until we find the device, or we exceed the verifyTimeout value.
|
|
9468
9794
|
if (loopTotalDelay > verifyTimeout):
|
|
9469
|
-
_logsi.LogWarning("Verification timeout waiting for Spotify Connect device %s to be added to the player device list; device not found within %f seconds of Connect" % (scDevice.Title, verifyTimeout))
|
|
9795
|
+
_logsi.LogWarning("Verification timeout waiting for Spotify Connect device %s to be added to the player device list or become the active device; device not found within %f seconds of Connect" % (scDevice.Title, verifyTimeout))
|
|
9470
9796
|
break
|
|
9471
9797
|
|
|
9472
9798
|
# return the device instance.
|
|
@@ -9544,7 +9870,7 @@ class SpotifyClient:
|
|
|
9544
9870
|
# trace.
|
|
9545
9871
|
apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
|
|
9546
9872
|
apiMethodParms.AppendKeyValue("refresh", refresh)
|
|
9547
|
-
apiMethodParms.AppendKeyValue("sortResult", sortResult)
|
|
9873
|
+
apiMethodParms.AppendKeyValue("sortResult (DEPRECATED)", sortResult)
|
|
9548
9874
|
_logsi.LogMethodParmList(SILevel.Verbose, "Get available Spotify Connect devices", apiMethodParms)
|
|
9549
9875
|
|
|
9550
9876
|
# validations.
|
|
@@ -11171,11 +11497,14 @@ class SpotifyClient:
|
|
|
11171
11497
|
This method requires the `user-modify-playback-state` scope.
|
|
11172
11498
|
|
|
11173
11499
|
Args:
|
|
11174
|
-
deviceId (str):
|
|
11175
|
-
The
|
|
11176
|
-
|
|
11177
|
-
|
|
11178
|
-
|
|
11500
|
+
deviceId (str | SpotifyConnectDevice) | None):
|
|
11501
|
+
The target player device identifier.
|
|
11502
|
+
This could be an id, name, a default device indicator (e.g. "*"), a
|
|
11503
|
+
SpotifyConnectDevice object, or null to utilize the active player device.
|
|
11504
|
+
A device is considered resolved if a SpotifyConnectDevice object is passed
|
|
11505
|
+
for this argument. An exception will be raised if the argument value could
|
|
11506
|
+
not be resolved or activated.
|
|
11507
|
+
Examples are `0d1841b0976bae2a3a310dd74c0f3df354899bc8`, `Office`, `*`, None.
|
|
11179
11508
|
delay (float):
|
|
11180
11509
|
Time delay (in seconds) to wait AFTER issuing the command to the player.
|
|
11181
11510
|
This delay will give the spotify web api time to process the change before
|
|
@@ -11215,17 +11544,27 @@ class SpotifyClient:
|
|
|
11215
11544
|
delay = validateDelay(delay, 0.50, 10)
|
|
11216
11545
|
|
|
11217
11546
|
# resolve / activate the device object if needed.
|
|
11218
|
-
scDevice:SpotifyConnectDevice = self._ResolveDeviceObject(deviceId)
|
|
11547
|
+
scDevice:SpotifyConnectDevice = self._ResolveDeviceObject(deviceId, False)
|
|
11219
11548
|
|
|
11220
|
-
#
|
|
11221
|
-
|
|
11222
|
-
urlParms['device_id'] = scDevice.Id
|
|
11549
|
+
# is this an active Sonos device?
|
|
11550
|
+
if (scDevice is not None) and (scDevice.IsSonos):
|
|
11223
11551
|
|
|
11224
|
-
|
|
11225
|
-
|
|
11226
|
-
|
|
11227
|
-
|
|
11228
|
-
|
|
11552
|
+
_logsi.LogVerbose("Issuing command to Sonos device %s: PAUSE" % (scDevice.Title))
|
|
11553
|
+
sonosPlayer:SoCo = self.SpotifyConnectDirectory.GetSonosPlayer(scDevice)
|
|
11554
|
+
sonosPlayer.pause()
|
|
11555
|
+
|
|
11556
|
+
else:
|
|
11557
|
+
|
|
11558
|
+
# build spotify web api request parameters.
|
|
11559
|
+
urlParms:dict = {}
|
|
11560
|
+
if (scDevice is not None):
|
|
11561
|
+
urlParms['device_id'] = scDevice.Id
|
|
11562
|
+
|
|
11563
|
+
# execute spotify web api request.
|
|
11564
|
+
msg:SpotifyApiMessage = SpotifyApiMessage(apiMethodName, '/me/player/pause')
|
|
11565
|
+
msg.RequestHeaders[self.AuthToken.HeaderKey] = self.AuthToken.HeaderValue
|
|
11566
|
+
msg.UrlParameters = urlParms
|
|
11567
|
+
self.MakeRequest('PUT', msg)
|
|
11229
11568
|
|
|
11230
11569
|
# give spotify web api time to process the change.
|
|
11231
11570
|
if delay > 0:
|
|
@@ -11288,11 +11627,14 @@ class SpotifyClient:
|
|
|
11288
11627
|
player to start playing the next track.
|
|
11289
11628
|
Default is `0`.
|
|
11290
11629
|
Example: `25000`
|
|
11291
|
-
deviceId (str):
|
|
11292
|
-
The
|
|
11293
|
-
|
|
11294
|
-
|
|
11295
|
-
|
|
11630
|
+
deviceId (str | SpotifyConnectDevice) | None):
|
|
11631
|
+
The target player device identifier.
|
|
11632
|
+
This could be an id, name, a default device indicator (e.g. "*"), a
|
|
11633
|
+
SpotifyConnectDevice object, or null to utilize the active player device.
|
|
11634
|
+
A device is considered resolved if a SpotifyConnectDevice object is passed
|
|
11635
|
+
for this argument. An exception will be raised if the argument value could
|
|
11636
|
+
not be resolved or activated.
|
|
11637
|
+
Examples are `0d1841b0976bae2a3a310dd74c0f3df354899bc8`, `Office`, `*`, None.
|
|
11296
11638
|
delay (float):
|
|
11297
11639
|
Time delay (in seconds) to wait AFTER issuing the command to the player.
|
|
11298
11640
|
This delay will give the spotify web api time to process the change before
|
|
@@ -11357,7 +11699,7 @@ class SpotifyClient:
|
|
|
11357
11699
|
delay = validateDelay(delay, 0.50, 10)
|
|
11358
11700
|
|
|
11359
11701
|
# resolve / activate the device object if needed.
|
|
11360
|
-
scDevice = self._ResolveDeviceObject(deviceId)
|
|
11702
|
+
scDevice = self._ResolveDeviceObject(deviceId, False)
|
|
11361
11703
|
|
|
11362
11704
|
# build spotify web api request parameters.
|
|
11363
11705
|
reqData:dict = \
|
|
@@ -11371,7 +11713,9 @@ class SpotifyClient:
|
|
|
11371
11713
|
reqData['offset'] = { 'position': offsetPosition }
|
|
11372
11714
|
|
|
11373
11715
|
# formulate url parms; will manually add them since we are adding json body as well.
|
|
11374
|
-
urlParms:str = '
|
|
11716
|
+
urlParms:str = ''
|
|
11717
|
+
if (scDevice is not None):
|
|
11718
|
+
urlParms = '?device_id=%s' % scDevice.Id
|
|
11375
11719
|
|
|
11376
11720
|
# execute spotify web api request.
|
|
11377
11721
|
msg:SpotifyApiMessage = SpotifyApiMessage(apiMethodName, '/me/player/play{url_parms}'.format(url_parms=urlParms))
|
|
@@ -11415,11 +11759,14 @@ class SpotifyClient:
|
|
|
11415
11759
|
and starts playing them.
|
|
11416
11760
|
|
|
11417
11761
|
Args:
|
|
11418
|
-
deviceId (str):
|
|
11419
|
-
The
|
|
11420
|
-
|
|
11421
|
-
|
|
11422
|
-
|
|
11762
|
+
deviceId (str | SpotifyConnectDevice) | None):
|
|
11763
|
+
The target player device identifier.
|
|
11764
|
+
This could be an id, name, a default device indicator (e.g. "*"), a
|
|
11765
|
+
SpotifyConnectDevice object, or null to utilize the active player device.
|
|
11766
|
+
A device is considered resolved if a SpotifyConnectDevice object is passed
|
|
11767
|
+
for this argument. An exception will be raised if the argument value could
|
|
11768
|
+
not be resolved or activated.
|
|
11769
|
+
Examples are `0d1841b0976bae2a3a310dd74c0f3df354899bc8`, `Office`, `*`, None.
|
|
11423
11770
|
shuffle (bool):
|
|
11424
11771
|
True to set player shuffle mode to on; otherwise, False for no shuffle.
|
|
11425
11772
|
delay (float):
|
|
@@ -11497,11 +11844,11 @@ class SpotifyClient:
|
|
|
11497
11844
|
arrUris.append(trackSaved.Track.Uri)
|
|
11498
11845
|
|
|
11499
11846
|
# resolve / activate the device object if needed.
|
|
11500
|
-
scDevice = self._ResolveDeviceObject(deviceId)
|
|
11847
|
+
scDevice = self._ResolveDeviceObject(deviceId, False)
|
|
11501
11848
|
|
|
11502
11849
|
# set desired shuffle mode.
|
|
11503
11850
|
if (shuffle == True):
|
|
11504
|
-
self.PlayerSetShuffleMode(shuffle, scDevice
|
|
11851
|
+
self.PlayerSetShuffleMode(shuffle, scDevice, delay)
|
|
11505
11852
|
|
|
11506
11853
|
# play the tracks.
|
|
11507
11854
|
# indicate device id has already been resolved.
|
|
@@ -11551,11 +11898,14 @@ class SpotifyClient:
|
|
|
11551
11898
|
player to start playing the next track.
|
|
11552
11899
|
Default is `0`.
|
|
11553
11900
|
Example: `25000`
|
|
11554
|
-
deviceId (str):
|
|
11555
|
-
The
|
|
11556
|
-
|
|
11557
|
-
|
|
11558
|
-
|
|
11901
|
+
deviceId (str | SpotifyConnectDevice) | None):
|
|
11902
|
+
The target player device identifier.
|
|
11903
|
+
This could be an id, name, a default device indicator (e.g. "*"), a
|
|
11904
|
+
SpotifyConnectDevice object, or null to utilize the active player device.
|
|
11905
|
+
A device is considered resolved if a SpotifyConnectDevice object is passed
|
|
11906
|
+
for this argument. An exception will be raised if the argument value could
|
|
11907
|
+
not be resolved or activated.
|
|
11908
|
+
Examples are `0d1841b0976bae2a3a310dd74c0f3df354899bc8`, `Office`, `*`, None.
|
|
11559
11909
|
delay (float):
|
|
11560
11910
|
Time delay (in seconds) to wait AFTER issuing the command to the player.
|
|
11561
11911
|
This delay will give the spotify web api time to process the change before
|
|
@@ -11614,7 +11964,7 @@ class SpotifyClient:
|
|
|
11614
11964
|
arrUris[idx] = arrUris[idx].strip()
|
|
11615
11965
|
|
|
11616
11966
|
# resolve / activate the device object if needed.
|
|
11617
|
-
scDevice = self._ResolveDeviceObject(deviceId)
|
|
11967
|
+
scDevice = self._ResolveDeviceObject(deviceId, False)
|
|
11618
11968
|
|
|
11619
11969
|
# left this here in case we want to implement it in the future.
|
|
11620
11970
|
|
|
@@ -11644,7 +11994,9 @@ class SpotifyClient:
|
|
|
11644
11994
|
reqData['uris'] = arrUris
|
|
11645
11995
|
|
|
11646
11996
|
# formulate url parms; will manually add them since we are adding json body as well.
|
|
11647
|
-
urlParms:str = '
|
|
11997
|
+
urlParms:str = ''
|
|
11998
|
+
if (scDevice is not None):
|
|
11999
|
+
urlParms = '?device_id=%s' % scDevice.Id
|
|
11648
12000
|
|
|
11649
12001
|
# execute spotify web api request.
|
|
11650
12002
|
msg:SpotifyApiMessage = SpotifyApiMessage(apiMethodName, '/me/player/play{url_parms}'.format(url_parms=urlParms))
|
|
@@ -11686,11 +12038,14 @@ class SpotifyClient:
|
|
|
11686
12038
|
This method requires the `user-modify-playback-state` scope.
|
|
11687
12039
|
|
|
11688
12040
|
Args:
|
|
11689
|
-
deviceId (str):
|
|
11690
|
-
The
|
|
11691
|
-
|
|
11692
|
-
|
|
11693
|
-
|
|
12041
|
+
deviceId (str | SpotifyConnectDevice) | None):
|
|
12042
|
+
The target player device identifier.
|
|
12043
|
+
This could be an id, name, a default device indicator (e.g. "*"), a
|
|
12044
|
+
SpotifyConnectDevice object, or null to utilize the active player device.
|
|
12045
|
+
A device is considered resolved if a SpotifyConnectDevice object is passed
|
|
12046
|
+
for this argument. An exception will be raised if the argument value could
|
|
12047
|
+
not be resolved or activated.
|
|
12048
|
+
Examples are `0d1841b0976bae2a3a310dd74c0f3df354899bc8`, `Office`, `*`, None.
|
|
11694
12049
|
delay (float):
|
|
11695
12050
|
Time delay (in seconds) to wait AFTER issuing the command to the player.
|
|
11696
12051
|
This delay will give the spotify web api time to process the change before
|
|
@@ -11707,7 +12062,7 @@ class SpotifyClient:
|
|
|
11707
12062
|
This API only works for users who have Spotify Premium.
|
|
11708
12063
|
|
|
11709
12064
|
The order of execution is not guaranteed when you use this API with other Player API endpoints.
|
|
11710
|
-
|
|
12065
|
+
|
|
11711
12066
|
<details>
|
|
11712
12067
|
<summary>Sample Code</summary>
|
|
11713
12068
|
```python
|
|
@@ -11730,17 +12085,27 @@ class SpotifyClient:
|
|
|
11730
12085
|
delay = validateDelay(delay, 0.50, 10)
|
|
11731
12086
|
|
|
11732
12087
|
# resolve / activate the device object if needed.
|
|
11733
|
-
scDevice:SpotifyConnectDevice = self._ResolveDeviceObject(deviceId)
|
|
12088
|
+
scDevice:SpotifyConnectDevice = self._ResolveDeviceObject(deviceId, False)
|
|
11734
12089
|
|
|
11735
|
-
#
|
|
11736
|
-
|
|
11737
|
-
urlParms['device_id'] = scDevice.Id
|
|
12090
|
+
# is this an active Sonos device?
|
|
12091
|
+
if (scDevice is not None) and (scDevice.IsSonos):
|
|
11738
12092
|
|
|
11739
|
-
|
|
11740
|
-
|
|
11741
|
-
|
|
11742
|
-
|
|
11743
|
-
|
|
12093
|
+
_logsi.LogVerbose("Issuing command to Sonos device %s: PLAY (RESUME)" % (scDevice.Title))
|
|
12094
|
+
sonosPlayer:SoCo = self.SpotifyConnectDirectory.GetSonosPlayer(scDevice)
|
|
12095
|
+
sonosPlayer.play()
|
|
12096
|
+
|
|
12097
|
+
else:
|
|
12098
|
+
|
|
12099
|
+
# build spotify web api request parameters.
|
|
12100
|
+
urlParms:dict = {}
|
|
12101
|
+
if (scDevice is not None):
|
|
12102
|
+
urlParms['device_id'] = scDevice.Id
|
|
12103
|
+
|
|
12104
|
+
# execute spotify web api request.
|
|
12105
|
+
msg:SpotifyApiMessage = SpotifyApiMessage(apiMethodName, '/me/player/play')
|
|
12106
|
+
msg.RequestHeaders[self.AuthToken.HeaderKey] = self.AuthToken.HeaderValue
|
|
12107
|
+
msg.UrlParameters = urlParms
|
|
12108
|
+
self.MakeRequest('PUT', msg)
|
|
11744
12109
|
|
|
11745
12110
|
# give spotify web api time to process the change.
|
|
11746
12111
|
if delay > 0:
|
|
@@ -11785,11 +12150,14 @@ class SpotifyClient:
|
|
|
11785
12150
|
Passing in a position that is greater than the length of the track will cause the
|
|
11786
12151
|
player to start playing the next song.
|
|
11787
12152
|
Example = `25000` to start playing at the 25 second mark.
|
|
11788
|
-
deviceId (str):
|
|
11789
|
-
The
|
|
11790
|
-
|
|
11791
|
-
|
|
11792
|
-
|
|
12153
|
+
deviceId (str | SpotifyConnectDevice) | None):
|
|
12154
|
+
The target player device identifier.
|
|
12155
|
+
This could be an id, name, a default device indicator (e.g. "*"), a
|
|
12156
|
+
SpotifyConnectDevice object, or null to utilize the active player device.
|
|
12157
|
+
A device is considered resolved if a SpotifyConnectDevice object is passed
|
|
12158
|
+
for this argument. An exception will be raised if the argument value could
|
|
12159
|
+
not be resolved or activated.
|
|
12160
|
+
Examples are `0d1841b0976bae2a3a310dd74c0f3df354899bc8`, `Office`, `*`, None.
|
|
11793
12161
|
delay (float):
|
|
11794
12162
|
Time delay (in seconds) to wait AFTER issuing the command to the player.
|
|
11795
12163
|
This delay will give the spotify web api time to process the change before
|
|
@@ -11855,13 +12223,13 @@ class SpotifyClient:
|
|
|
11855
12223
|
relativePositionMS = 0
|
|
11856
12224
|
|
|
11857
12225
|
# resolve / activate the device object if needed.
|
|
11858
|
-
scDevice:SpotifyConnectDevice = self._ResolveDeviceObject(deviceId)
|
|
12226
|
+
scDevice:SpotifyConnectDevice = self._ResolveDeviceObject(deviceId, False)
|
|
11859
12227
|
|
|
11860
12228
|
# was relative seeking specified?
|
|
11861
|
-
if (relativePositionMS != 0) and ((positionMS is None) or (positionMS <= 0)):
|
|
12229
|
+
if (scDevice is not None) and (relativePositionMS != 0) and ((positionMS is None) or (positionMS <= 0)):
|
|
11862
12230
|
|
|
11863
|
-
# get current track position.
|
|
11864
|
-
playState:PlayerPlayState = self.
|
|
12231
|
+
# get current track position (from cache, since we called _ResolveDeviceObject method above).
|
|
12232
|
+
playState:PlayerPlayState = self.GetDevicePlaybackState(scDevice)
|
|
11865
12233
|
newPositionMS:int = playState.ProgressMS
|
|
11866
12234
|
if (newPositionMS is not None) and (newPositionMS > 0):
|
|
11867
12235
|
|
|
@@ -11871,18 +12239,29 @@ class SpotifyClient:
|
|
|
11871
12239
|
newPositionMS = 0
|
|
11872
12240
|
positionMS = newPositionMS
|
|
11873
12241
|
|
|
11874
|
-
#
|
|
11875
|
-
|
|
11876
|
-
{
|
|
11877
|
-
'position_ms': positionMS
|
|
11878
|
-
}
|
|
11879
|
-
urlParms['device_id'] = scDevice.Id
|
|
12242
|
+
# is this an active Sonos device?
|
|
12243
|
+
if (scDevice is not None) and (scDevice.IsSonos):
|
|
11880
12244
|
|
|
11881
|
-
|
|
11882
|
-
|
|
11883
|
-
|
|
11884
|
-
|
|
11885
|
-
|
|
12245
|
+
sonosPosition:str = mediaPositionHMS_fromSeconds(positionMS / 1000) # convert from milliseconds to Sonos H:MM:SS format
|
|
12246
|
+
_logsi.LogVerbose("Issuing command to Sonos device %s: SEEK (sonosPosition=%s)" % (scDevice.Title, sonosPosition))
|
|
12247
|
+
sonosPlayer:SoCo = self.SpotifyConnectDirectory.GetSonosPlayer(scDevice)
|
|
12248
|
+
sonosPlayer.seek(str(sonosPosition))
|
|
12249
|
+
|
|
12250
|
+
else:
|
|
12251
|
+
|
|
12252
|
+
# build spotify web api request parameters.
|
|
12253
|
+
urlParms:dict = \
|
|
12254
|
+
{
|
|
12255
|
+
'position_ms': positionMS
|
|
12256
|
+
}
|
|
12257
|
+
if (scDevice is not None):
|
|
12258
|
+
urlParms['device_id'] = scDevice.Id
|
|
12259
|
+
|
|
12260
|
+
# execute spotify web api request.
|
|
12261
|
+
msg:SpotifyApiMessage = SpotifyApiMessage(apiMethodName, '/me/player/seek')
|
|
12262
|
+
msg.RequestHeaders[self.AuthToken.HeaderKey] = self.AuthToken.HeaderValue
|
|
12263
|
+
msg.UrlParameters = urlParms
|
|
12264
|
+
self.MakeRequest('PUT', msg)
|
|
11886
12265
|
|
|
11887
12266
|
# give spotify web api time to process the change.
|
|
11888
12267
|
if delay > 0:
|
|
@@ -11918,11 +12297,14 @@ class SpotifyClient:
|
|
|
11918
12297
|
This method requires the `user-modify-playback-state` scope.
|
|
11919
12298
|
|
|
11920
12299
|
Args:
|
|
11921
|
-
deviceId (str):
|
|
11922
|
-
The
|
|
11923
|
-
|
|
11924
|
-
|
|
11925
|
-
|
|
12300
|
+
deviceId (str | SpotifyConnectDevice) | None):
|
|
12301
|
+
The target player device identifier.
|
|
12302
|
+
This could be an id, name, a default device indicator (e.g. "*"), a
|
|
12303
|
+
SpotifyConnectDevice object, or null to utilize the active player device.
|
|
12304
|
+
A device is considered resolved if a SpotifyConnectDevice object is passed
|
|
12305
|
+
for this argument. An exception will be raised if the argument value could
|
|
12306
|
+
not be resolved or activated.
|
|
12307
|
+
Examples are `0d1841b0976bae2a3a310dd74c0f3df354899bc8`, `Office`, `*`, None.
|
|
11926
12308
|
delay (float):
|
|
11927
12309
|
Time delay (in seconds) to wait AFTER issuing the command to the player.
|
|
11928
12310
|
This delay will give the spotify web api time to process the change before
|
|
@@ -11962,17 +12344,27 @@ class SpotifyClient:
|
|
|
11962
12344
|
delay = validateDelay(delay, 0.50, 10)
|
|
11963
12345
|
|
|
11964
12346
|
# resolve / activate the device object if needed.
|
|
11965
|
-
scDevice:SpotifyConnectDevice = self._ResolveDeviceObject(deviceId)
|
|
12347
|
+
scDevice:SpotifyConnectDevice = self._ResolveDeviceObject(deviceId, False)
|
|
11966
12348
|
|
|
11967
|
-
#
|
|
11968
|
-
|
|
11969
|
-
urlParms['device_id'] = scDevice.Id
|
|
12349
|
+
# is this an active Sonos device?
|
|
12350
|
+
if (scDevice is not None) and (scDevice.IsSonos):
|
|
11970
12351
|
|
|
11971
|
-
|
|
11972
|
-
|
|
11973
|
-
|
|
11974
|
-
|
|
11975
|
-
|
|
12352
|
+
_logsi.LogVerbose("Issuing command to Sonos device %s: NEXT" % (scDevice.Title))
|
|
12353
|
+
sonosPlayer:SoCo = self.SpotifyConnectDirectory.GetSonosPlayer(scDevice)
|
|
12354
|
+
sonosPlayer.next()
|
|
12355
|
+
|
|
12356
|
+
else:
|
|
12357
|
+
|
|
12358
|
+
# build spotify web api request parameters.
|
|
12359
|
+
urlParms:dict = {}
|
|
12360
|
+
if (scDevice is not None):
|
|
12361
|
+
urlParms['device_id'] = scDevice.Id
|
|
12362
|
+
|
|
12363
|
+
# execute spotify web api request.
|
|
12364
|
+
msg:SpotifyApiMessage = SpotifyApiMessage(apiMethodName, '/me/player/next')
|
|
12365
|
+
msg.RequestHeaders[self.AuthToken.HeaderKey] = self.AuthToken.HeaderValue
|
|
12366
|
+
msg.UrlParameters = urlParms
|
|
12367
|
+
self.MakeRequest('POST', msg)
|
|
11976
12368
|
|
|
11977
12369
|
# give spotify web api time to process the change.
|
|
11978
12370
|
if delay > 0:
|
|
@@ -12008,11 +12400,14 @@ class SpotifyClient:
|
|
|
12008
12400
|
This method requires the `user-modify-playback-state` scope.
|
|
12009
12401
|
|
|
12010
12402
|
Args:
|
|
12011
|
-
deviceId (str):
|
|
12012
|
-
The
|
|
12013
|
-
|
|
12014
|
-
|
|
12015
|
-
|
|
12403
|
+
deviceId (str | SpotifyConnectDevice) | None):
|
|
12404
|
+
The target player device identifier.
|
|
12405
|
+
This could be an id, name, a default device indicator (e.g. "*"), a
|
|
12406
|
+
SpotifyConnectDevice object, or null to utilize the active player device.
|
|
12407
|
+
A device is considered resolved if a SpotifyConnectDevice object is passed
|
|
12408
|
+
for this argument. An exception will be raised if the argument value could
|
|
12409
|
+
not be resolved or activated.
|
|
12410
|
+
Examples are `0d1841b0976bae2a3a310dd74c0f3df354899bc8`, `Office`, `*`, None.
|
|
12016
12411
|
delay (float):
|
|
12017
12412
|
Time delay (in seconds) to wait AFTER issuing the command to the player.
|
|
12018
12413
|
This delay will give the spotify web api time to process the change before
|
|
@@ -12052,17 +12447,27 @@ class SpotifyClient:
|
|
|
12052
12447
|
delay = validateDelay(delay, 0.50, 10)
|
|
12053
12448
|
|
|
12054
12449
|
# resolve / activate the device object if needed.
|
|
12055
|
-
scDevice:SpotifyConnectDevice = self._ResolveDeviceObject(deviceId)
|
|
12450
|
+
scDevice:SpotifyConnectDevice = self._ResolveDeviceObject(deviceId, False)
|
|
12056
12451
|
|
|
12057
|
-
#
|
|
12058
|
-
|
|
12059
|
-
urlParms['device_id'] = scDevice.Id
|
|
12452
|
+
# is this an active Sonos device?
|
|
12453
|
+
if (scDevice is not None) and (scDevice.IsSonos):
|
|
12060
12454
|
|
|
12061
|
-
|
|
12062
|
-
|
|
12063
|
-
|
|
12064
|
-
|
|
12065
|
-
|
|
12455
|
+
_logsi.LogVerbose("Issuing command to Sonos device %s: PREVIOUS" % (scDevice.Title))
|
|
12456
|
+
sonosPlayer:SoCo = self.SpotifyConnectDirectory.GetSonosPlayer(scDevice)
|
|
12457
|
+
sonosPlayer.previous()
|
|
12458
|
+
|
|
12459
|
+
else:
|
|
12460
|
+
|
|
12461
|
+
# build spotify web api request parameters.
|
|
12462
|
+
urlParms:dict = {}
|
|
12463
|
+
if (scDevice is not None):
|
|
12464
|
+
urlParms['device_id'] = scDevice.Id
|
|
12465
|
+
|
|
12466
|
+
# execute spotify web api request.
|
|
12467
|
+
msg:SpotifyApiMessage = SpotifyApiMessage(apiMethodName, '/me/player/previous')
|
|
12468
|
+
msg.RequestHeaders[self.AuthToken.HeaderKey] = self.AuthToken.HeaderValue
|
|
12469
|
+
msg.UrlParameters = urlParms
|
|
12470
|
+
self.MakeRequest('POST', msg)
|
|
12066
12471
|
|
|
12067
12472
|
# give spotify web api time to process the change.
|
|
12068
12473
|
if delay > 0:
|
|
@@ -12105,11 +12510,14 @@ class SpotifyClient:
|
|
|
12105
12510
|
- `context` - will repeat the current context.
|
|
12106
12511
|
- `off` - will turn repeat off.
|
|
12107
12512
|
Default: `off`
|
|
12108
|
-
deviceId (str):
|
|
12109
|
-
The
|
|
12110
|
-
|
|
12111
|
-
|
|
12112
|
-
|
|
12513
|
+
deviceId (str | SpotifyConnectDevice) | None):
|
|
12514
|
+
The target player device identifier.
|
|
12515
|
+
This could be an id, name, a default device indicator (e.g. "*"), a
|
|
12516
|
+
SpotifyConnectDevice object, or null to utilize the active player device.
|
|
12517
|
+
A device is considered resolved if a SpotifyConnectDevice object is passed
|
|
12518
|
+
for this argument. An exception will be raised if the argument value could
|
|
12519
|
+
not be resolved or activated.
|
|
12520
|
+
Examples are `0d1841b0976bae2a3a310dd74c0f3df354899bc8`, `Office`, `*`, None.
|
|
12113
12521
|
delay (float):
|
|
12114
12522
|
Time delay (in seconds) to wait AFTER issuing the command to the player.
|
|
12115
12523
|
This delay will give the spotify web api time to process the change before
|
|
@@ -12150,20 +12558,47 @@ class SpotifyClient:
|
|
|
12150
12558
|
delay = validateDelay(delay, 0.50, 10)
|
|
12151
12559
|
|
|
12152
12560
|
# resolve / activate the device object if needed.
|
|
12153
|
-
scDevice:SpotifyConnectDevice = self._ResolveDeviceObject(deviceId)
|
|
12561
|
+
scDevice:SpotifyConnectDevice = self._ResolveDeviceObject(deviceId, False)
|
|
12562
|
+
|
|
12563
|
+
# is this an active Sonos device?
|
|
12564
|
+
if (scDevice is not None) and (scDevice.IsSonos):
|
|
12565
|
+
|
|
12566
|
+
# get current Spotify Connect player state.
|
|
12567
|
+
playerState = self.GetPlayerPlaybackStateSonos(scDevice)
|
|
12568
|
+
|
|
12569
|
+
# set the Sonos PlayMode directly (instead of setting the Sonos repeat property)!
|
|
12570
|
+
# The Spotify Web API PlayerState `ShuffleState` value can sometimes be different than what the
|
|
12571
|
+
# Sonos device reports for its `shuffle` property value!
|
|
12572
|
+
if state == 'track':
|
|
12573
|
+
sonos_repeat = 'ONE'
|
|
12574
|
+
elif state == 'off':
|
|
12575
|
+
sonos_repeat = False
|
|
12576
|
+
elif state == 'context':
|
|
12577
|
+
sonos_repeat = True
|
|
12578
|
+
else:
|
|
12579
|
+
sonos_repeat = False
|
|
12580
|
+
playMode:str = SONOS_PLAY_MODE_BY_MEANING[(playerState.ShuffleState, sonos_repeat)]
|
|
12581
|
+
|
|
12582
|
+
# execute SoCo api request.
|
|
12583
|
+
_logsi.LogVerbose("Issuing command to Sonos device %s: REPEAT (playmode=%s)" % (scDevice.Title, playMode))
|
|
12584
|
+
sonosPlayer:SoCo = self.SpotifyConnectDirectory.GetSonosPlayer(scDevice)
|
|
12585
|
+
sonosPlayer.play_mode = playMode
|
|
12154
12586
|
|
|
12155
|
-
|
|
12156
|
-
urlParms:dict = \
|
|
12157
|
-
{
|
|
12158
|
-
'state': state
|
|
12159
|
-
}
|
|
12160
|
-
urlParms['device_id'] = scDevice.Id
|
|
12587
|
+
else:
|
|
12161
12588
|
|
|
12162
|
-
|
|
12163
|
-
|
|
12164
|
-
|
|
12165
|
-
|
|
12166
|
-
|
|
12589
|
+
# build spotify web api request parameters.
|
|
12590
|
+
urlParms:dict = \
|
|
12591
|
+
{
|
|
12592
|
+
'state': state
|
|
12593
|
+
}
|
|
12594
|
+
if (scDevice is not None):
|
|
12595
|
+
urlParms['device_id'] = scDevice.Id
|
|
12596
|
+
|
|
12597
|
+
# execute spotify web api request.
|
|
12598
|
+
msg:SpotifyApiMessage = SpotifyApiMessage(apiMethodName, '/me/player/repeat')
|
|
12599
|
+
msg.RequestHeaders[self.AuthToken.HeaderKey] = self.AuthToken.HeaderValue
|
|
12600
|
+
msg.UrlParameters = urlParms
|
|
12601
|
+
self.MakeRequest('PUT', msg)
|
|
12167
12602
|
|
|
12168
12603
|
# give spotify web api time to process the change.
|
|
12169
12604
|
if delay > 0:
|
|
@@ -12205,11 +12640,14 @@ class SpotifyClient:
|
|
|
12205
12640
|
- `True` - Shuffle user's playback.
|
|
12206
12641
|
- `False` - Do not shuffle user's playback.
|
|
12207
12642
|
Default is False
|
|
12208
|
-
deviceId (str):
|
|
12209
|
-
The
|
|
12210
|
-
|
|
12211
|
-
|
|
12212
|
-
|
|
12643
|
+
deviceId (str | SpotifyConnectDevice) | None):
|
|
12644
|
+
The target player device identifier.
|
|
12645
|
+
This could be an id, name, a default device indicator (e.g. "*"), a
|
|
12646
|
+
SpotifyConnectDevice object, or null to utilize the active player device.
|
|
12647
|
+
A device is considered resolved if a SpotifyConnectDevice object is passed
|
|
12648
|
+
for this argument. An exception will be raised if the argument value could
|
|
12649
|
+
not be resolved or activated.
|
|
12650
|
+
Examples are `0d1841b0976bae2a3a310dd74c0f3df354899bc8`, `Office`, `*`, None.
|
|
12213
12651
|
delay (float):
|
|
12214
12652
|
Time delay (in seconds) to wait AFTER issuing the command to the player.
|
|
12215
12653
|
This delay will give the spotify web api time to process the change before
|
|
@@ -12250,20 +12688,48 @@ class SpotifyClient:
|
|
|
12250
12688
|
delay = validateDelay(delay, 0.50, 10)
|
|
12251
12689
|
|
|
12252
12690
|
# resolve / activate the device object if needed.
|
|
12253
|
-
scDevice:SpotifyConnectDevice = self._ResolveDeviceObject(deviceId)
|
|
12691
|
+
scDevice:SpotifyConnectDevice = self._ResolveDeviceObject(deviceId, False)
|
|
12692
|
+
|
|
12693
|
+
# is this an active Sonos device?
|
|
12694
|
+
if (scDevice is not None) and (scDevice.IsSonos):
|
|
12695
|
+
|
|
12696
|
+
# get current Spotify Connect player state.
|
|
12697
|
+
playerState = self.GetPlayerPlaybackStateSonos(scDevice)
|
|
12698
|
+
|
|
12699
|
+
# set the Sonos PlayMode directly (instead of setting the Sonos `shuffle` property)!
|
|
12700
|
+
# the Spotify Web API PlayerState `RepeatState` value can sometimes be different than what the
|
|
12701
|
+
# Sonos device reports for its `repeat` property value!
|
|
12702
|
+
sonos_repeat:str
|
|
12703
|
+
if playerState.RepeatState == 'track':
|
|
12704
|
+
sonos_repeat = 'ONE'
|
|
12705
|
+
elif playerState.RepeatState == 'off':
|
|
12706
|
+
sonos_repeat = False
|
|
12707
|
+
elif playerState.RepeatState == 'context':
|
|
12708
|
+
sonos_repeat = True
|
|
12709
|
+
else: # assume off if nothing else.
|
|
12710
|
+
sonos_repeat = False
|
|
12711
|
+
playMode:str = SONOS_PLAY_MODE_BY_MEANING[(state, sonos_repeat)]
|
|
12712
|
+
|
|
12713
|
+
# execute SoCo api request.
|
|
12714
|
+
_logsi.LogVerbose("Issuing command to Sonos device %s: SHUFFLE (playmode=%s)" % (scDevice.Title, playMode))
|
|
12715
|
+
sonosPlayer:SoCo = self.SpotifyConnectDirectory.GetSonosPlayer(scDevice)
|
|
12716
|
+
sonosPlayer.play_mode = playMode
|
|
12254
12717
|
|
|
12255
|
-
|
|
12256
|
-
urlParms:dict = \
|
|
12257
|
-
{
|
|
12258
|
-
'state': state
|
|
12259
|
-
}
|
|
12260
|
-
urlParms['device_id'] = scDevice.Id
|
|
12718
|
+
else:
|
|
12261
12719
|
|
|
12262
|
-
|
|
12263
|
-
|
|
12264
|
-
|
|
12265
|
-
|
|
12266
|
-
|
|
12720
|
+
# build spotify web api request parameters.
|
|
12721
|
+
urlParms:dict = \
|
|
12722
|
+
{
|
|
12723
|
+
'state': state
|
|
12724
|
+
}
|
|
12725
|
+
if (scDevice is not None):
|
|
12726
|
+
urlParms['device_id'] = scDevice.Id
|
|
12727
|
+
|
|
12728
|
+
# execute spotify web api request.
|
|
12729
|
+
msg:SpotifyApiMessage = SpotifyApiMessage(apiMethodName, '/me/player/shuffle')
|
|
12730
|
+
msg.RequestHeaders[self.AuthToken.HeaderKey] = self.AuthToken.HeaderValue
|
|
12731
|
+
msg.UrlParameters = urlParms
|
|
12732
|
+
self.MakeRequest('PUT', msg)
|
|
12267
12733
|
|
|
12268
12734
|
# give spotify web api time to process the change.
|
|
12269
12735
|
if delay > 0:
|
|
@@ -12304,11 +12770,14 @@ class SpotifyClient:
|
|
|
12304
12770
|
The volume to set.
|
|
12305
12771
|
Must be a value from 0 to 100 inclusive.
|
|
12306
12772
|
Example: `50`
|
|
12307
|
-
deviceId (str):
|
|
12308
|
-
The
|
|
12309
|
-
|
|
12310
|
-
|
|
12311
|
-
|
|
12773
|
+
deviceId (str | SpotifyConnectDevice) | None):
|
|
12774
|
+
The target player device identifier.
|
|
12775
|
+
This could be an id, name, a default device indicator (e.g. "*"), a
|
|
12776
|
+
SpotifyConnectDevice object, or null to utilize the active player device.
|
|
12777
|
+
A device is considered resolved if a SpotifyConnectDevice object is passed
|
|
12778
|
+
for this argument. An exception will be raised if the argument value could
|
|
12779
|
+
not be resolved or activated.
|
|
12780
|
+
Examples are `0d1841b0976bae2a3a310dd74c0f3df354899bc8`, `Office`, `*`, None.
|
|
12312
12781
|
delay (float):
|
|
12313
12782
|
Time delay (in seconds) to wait AFTER issuing the command to the player.
|
|
12314
12783
|
This delay will give the spotify web api time to process the change before
|
|
@@ -12349,20 +12818,30 @@ class SpotifyClient:
|
|
|
12349
12818
|
delay = validateDelay(delay, 0.50, 10)
|
|
12350
12819
|
|
|
12351
12820
|
# resolve / activate the device object if needed.
|
|
12352
|
-
scDevice:SpotifyConnectDevice = self._ResolveDeviceObject(deviceId)
|
|
12821
|
+
scDevice:SpotifyConnectDevice = self._ResolveDeviceObject(deviceId, False)
|
|
12353
12822
|
|
|
12354
|
-
#
|
|
12355
|
-
|
|
12356
|
-
{
|
|
12357
|
-
'volume_percent': volumePercent
|
|
12358
|
-
}
|
|
12359
|
-
urlParms['device_id'] = scDevice.Id
|
|
12823
|
+
# is this an active Sonos device?
|
|
12824
|
+
if (scDevice is not None) and (scDevice.IsSonos):
|
|
12360
12825
|
|
|
12361
|
-
|
|
12362
|
-
|
|
12363
|
-
|
|
12364
|
-
|
|
12365
|
-
|
|
12826
|
+
_logsi.LogVerbose("Issuing command to Sonos device %s: VOLUME = %s" % (scDevice.Title, volumePercent))
|
|
12827
|
+
sonosPlayer:SoCo = self.SpotifyConnectDirectory.GetSonosPlayer(scDevice)
|
|
12828
|
+
sonosPlayer.volume = volumePercent
|
|
12829
|
+
|
|
12830
|
+
else:
|
|
12831
|
+
|
|
12832
|
+
# build spotify web api request parameters.
|
|
12833
|
+
urlParms:dict = \
|
|
12834
|
+
{
|
|
12835
|
+
'volume_percent': volumePercent
|
|
12836
|
+
}
|
|
12837
|
+
if (scDevice is not None):
|
|
12838
|
+
urlParms['device_id'] = scDevice.Id
|
|
12839
|
+
|
|
12840
|
+
# execute spotify web api request.
|
|
12841
|
+
msg:SpotifyApiMessage = SpotifyApiMessage(apiMethodName, '/me/player/volume')
|
|
12842
|
+
msg.RequestHeaders[self.AuthToken.HeaderKey] = self.AuthToken.HeaderValue
|
|
12843
|
+
msg.UrlParameters = urlParms
|
|
12844
|
+
self.MakeRequest('PUT', msg)
|
|
12366
12845
|
|
|
12367
12846
|
# give spotify web api time to process the change.
|
|
12368
12847
|
if delay > 0:
|
|
@@ -12394,6 +12873,7 @@ class SpotifyClient:
|
|
|
12394
12873
|
delay:float=0.50,
|
|
12395
12874
|
refreshDeviceList:bool=True,
|
|
12396
12875
|
forceActivateDevice:bool=True,
|
|
12876
|
+
deviceIdFrom:str=None,
|
|
12397
12877
|
) -> None:
|
|
12398
12878
|
"""
|
|
12399
12879
|
Transfer playback to a new Spotify Connect device and optionally begin playback.
|
|
@@ -12401,10 +12881,14 @@ class SpotifyClient:
|
|
|
12401
12881
|
This method requires the `user-modify-playback-state` scope.
|
|
12402
12882
|
|
|
12403
12883
|
Args:
|
|
12404
|
-
deviceId (str):
|
|
12405
|
-
The
|
|
12406
|
-
|
|
12407
|
-
|
|
12884
|
+
deviceId (str | SpotifyConnectDevice) | None):
|
|
12885
|
+
The target player device identifier.
|
|
12886
|
+
This could be an id, name, a default device indicator (e.g. "*"), a
|
|
12887
|
+
SpotifyConnectDevice object, or null to utilize the active player device.
|
|
12888
|
+
A device is considered resolved if a SpotifyConnectDevice object is passed
|
|
12889
|
+
for this argument. An exception will be raised if the argument value could
|
|
12890
|
+
not be resolved or activated.
|
|
12891
|
+
Examples are `0d1841b0976bae2a3a310dd74c0f3df354899bc8`, `Office`, `*`, None.
|
|
12408
12892
|
play (bool):
|
|
12409
12893
|
The transfer method:
|
|
12410
12894
|
- `True` - ensure playback happens on new device.
|
|
@@ -12424,6 +12908,13 @@ class SpotifyClient:
|
|
|
12424
12908
|
force the device to reconnect to Spotify Connect; otherwise, False to not
|
|
12425
12909
|
disconnect.
|
|
12426
12910
|
Default is True.
|
|
12911
|
+
deviceIdFrom (str | SpotifyConnectDevice) | None):
|
|
12912
|
+
The player device identifier where play is being transferred from.
|
|
12913
|
+
This could be an id, name, a default device indicator (e.g. "*"), a
|
|
12914
|
+
SpotifyConnectDevice object, or null.
|
|
12915
|
+
A device is considered resolved if a SpotifyConnectDevice object is passed
|
|
12916
|
+
for this argument.
|
|
12917
|
+
Examples are `0d1841b0976bae2a3a310dd74c0f3df354899bc8`, `Office`, `*`, None.
|
|
12427
12918
|
|
|
12428
12919
|
Raises:
|
|
12429
12920
|
SpotifyWebApiError:
|
|
@@ -12435,6 +12926,11 @@ class SpotifyClient:
|
|
|
12435
12926
|
This API only works for users who have Spotify Premium.
|
|
12436
12927
|
|
|
12437
12928
|
The order of execution is not guaranteed when you use this API with other Player API endpoints.
|
|
12929
|
+
|
|
12930
|
+
The `deviceIdFrom` argument is not part of the Spotify Web API specification. If a value
|
|
12931
|
+
was supplied, then we will attempt to stop play on the device if it's needed. This is required
|
|
12932
|
+
if transferring playback from a Sonos device that is playing content on its local queue; if you
|
|
12933
|
+
don't stop playback on it, then it and the target device will both be playing!
|
|
12438
12934
|
|
|
12439
12935
|
<details>
|
|
12440
12936
|
<summary>Sample Code</summary>
|
|
@@ -12456,6 +12952,7 @@ class SpotifyClient:
|
|
|
12456
12952
|
apiMethodParms.AppendKeyValue("delay", delay)
|
|
12457
12953
|
apiMethodParms.AppendKeyValue("refreshDeviceList", refreshDeviceList)
|
|
12458
12954
|
apiMethodParms.AppendKeyValue("forceActivateDevice", forceActivateDevice)
|
|
12955
|
+
apiMethodParms.AppendKeyValue("deviceIdFrom", deviceIdFrom)
|
|
12459
12956
|
_logsi.LogMethodParmList(SILevel.Verbose, "Transfer playback to a new Spotify Connect device", apiMethodParms)
|
|
12460
12957
|
|
|
12461
12958
|
# validations.
|
|
@@ -12467,15 +12964,69 @@ class SpotifyClient:
|
|
|
12467
12964
|
if (forceActivateDevice is None):
|
|
12468
12965
|
forceActivateDevice = True
|
|
12469
12966
|
|
|
12967
|
+
# was a from deviceId specified? if so, then we will attempt to stop play on the device
|
|
12968
|
+
# if it's needed. this is required if transferring playback from a Sonos device that is
|
|
12969
|
+
# playing content on its local queue; if you don't stop playback on it, then it and the
|
|
12970
|
+
# target device will both be playing!
|
|
12971
|
+
if (deviceIdFrom is not None):
|
|
12972
|
+
|
|
12973
|
+
# trace.
|
|
12974
|
+
_logsi.LogVerbose("FROM deviceId supplied; checking to see if we need to pause play on device \"%s\" prior to transferring playback to device \"%s\"" % (deviceIdFrom, deviceId))
|
|
12975
|
+
|
|
12976
|
+
# resolve from device object; no need to activate it for what we want here.
|
|
12977
|
+
scDeviceFrom:SpotifyConnectDevice = self._ResolveDeviceObject(deviceIdFrom, activateDevice=False)
|
|
12978
|
+
|
|
12979
|
+
# if it's a Sonos, then pause the player.
|
|
12980
|
+
# note that for other brands, the Spotify Web API transfer playback takes care of
|
|
12981
|
+
# pausing the player since everything is under the Spotify players control.
|
|
12982
|
+
if (scDeviceFrom is not None) and (scDeviceFrom.IsSonos):
|
|
12983
|
+
self.PlayerMediaPause(scDeviceFrom)
|
|
12984
|
+
|
|
12470
12985
|
# resolve / activate the device object if needed.
|
|
12471
|
-
|
|
12986
|
+
# for transfer playback, we will always activate the device.
|
|
12987
|
+
scDevice = self._ResolveDeviceObject(deviceId, True)
|
|
12988
|
+
|
|
12989
|
+
# if device could not be resolved, then go no further as it will result in a
|
|
12990
|
+
# "device not found" error from the Spotify Web API anyway!
|
|
12991
|
+
if (scDevice is None):
|
|
12992
|
+
raise SpotifyConnectDeviceNotFound("Spotify Player device was not found, and there is no active Spotify player", logsi=_logsi)
|
|
12472
12993
|
|
|
12473
12994
|
# is this a Sonos device?
|
|
12474
12995
|
# if so, then control was automatically transferred by activating the device.
|
|
12475
|
-
# note that
|
|
12476
|
-
# considered a restricted device and the Spotify Web API cannot control it!
|
|
12477
|
-
|
|
12478
|
-
|
|
12996
|
+
# note that the Spotify Web API cannot stop nor start play on the device either,
|
|
12997
|
+
# as it's considered a restricted device and the Spotify Web API cannot control it!
|
|
12998
|
+
# all requests to control the device will be made via the Sonos Controller API.
|
|
12999
|
+
if (scDevice.IsSonos):
|
|
13000
|
+
|
|
13001
|
+
# get Sonos Controller instance for the device.
|
|
13002
|
+
sonosPlayer:SoCo = self.SpotifyConnectDirectory.GetSonosPlayer(scDevice)
|
|
13003
|
+
|
|
13004
|
+
# get current Sonos transport status.
|
|
13005
|
+
sonosTransportInfo:dict = sonosPlayer.get_current_transport_info()
|
|
13006
|
+
currentTransportState:str = sonosTransportInfo.get('current_transport_state', None)
|
|
13007
|
+
currentTransportStatus:str = sonosTransportInfo.get('current_transport_status', None)
|
|
13008
|
+
_logsi.LogVerbose("Sonos device %s current transport state after activation: \"%s\" (Status=%s)" % (scDevice.Title, currentTransportState, currentTransportStatus))
|
|
13009
|
+
|
|
13010
|
+
# stop / start play as requested.
|
|
13011
|
+
wasCmdIssued:bool = False
|
|
13012
|
+
if currentTransportState == 'PLAYING':
|
|
13013
|
+
if play == False:
|
|
13014
|
+
_logsi.LogVerbose("Issuing command to Sonos device %s: PAUSE" % (scDevice.Title))
|
|
13015
|
+
sonosPlayer.pause()
|
|
13016
|
+
wasCmdIssued = True
|
|
13017
|
+
elif currentTransportState in ['PAUSED_PLAYBACK','STOPPED','TRANSITIONING']:
|
|
13018
|
+
if play == True:
|
|
13019
|
+
_logsi.LogVerbose("Issuing command to Sonos device %s: PLAY" % (scDevice.Title))
|
|
13020
|
+
sonosPlayer.play()
|
|
13021
|
+
wasCmdIssued = True
|
|
13022
|
+
|
|
13023
|
+
# give SoCo api time to process the change.
|
|
13024
|
+
if (wasCmdIssued) and (delay > 0):
|
|
13025
|
+
_logsi.LogVerbose(TRACE_MSG_DELAY_DEVICE_SONOS % delay)
|
|
13026
|
+
time.sleep(delay)
|
|
13027
|
+
|
|
13028
|
+
# trace.
|
|
13029
|
+
_logsi.LogVerbose("Sonos device detected (IsRestricted); bypassing call to Spotify Web API Transfer Playback endpoint")
|
|
12479
13030
|
return
|
|
12480
13031
|
|
|
12481
13032
|
# before transferring playback to the device, we first need to check to see if anything
|