spotifywebapiPython 1.0.151__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.

Files changed (24) hide show
  1. {spotifywebapiPython-1.0.151.dist-info → spotifywebapiPython-1.0.152.dist-info}/METADATA +1 -1
  2. {spotifywebapiPython-1.0.151.dist-info → spotifywebapiPython-1.0.152.dist-info}/RECORD +24 -24
  3. spotifywebapipython/const.py +1 -1
  4. spotifywebapipython/docs/__init__.html +8 -2
  5. spotifywebapipython/docs/index.html +2 -2
  6. spotifywebapipython/docs/search.js +1 -1
  7. spotifywebapipython/docs/spotifywebapipython/const.html +3 -3
  8. spotifywebapipython/docs/spotifywebapipython/saappmessages.html +2 -2
  9. spotifywebapipython/docs/spotifywebapipython/sautils.html +2 -2
  10. spotifywebapipython/docs/spotifywebapipython/spotifyapierror.html +2 -2
  11. spotifywebapipython/docs/spotifywebapipython/spotifyapimessage.html +2 -2
  12. spotifywebapipython/docs/spotifywebapipython/spotifyauthtoken.html +2 -2
  13. spotifywebapipython/docs/spotifywebapipython/spotifyclient.html +337 -160
  14. spotifywebapipython/docs/spotifywebapipython/spotifydiscovery.html +2 -2
  15. spotifywebapipython/docs/spotifywebapipython/spotifywebapiauthenticationerror.html +2 -2
  16. spotifywebapipython/docs/spotifywebapipython/spotifywebapierror.html +2 -2
  17. spotifywebapipython/docs/spotifywebapipython/spotifywebplayertoken.html +2 -2
  18. spotifywebapipython/models/playerplaystate.py +42 -0
  19. spotifywebapipython/spotifyclient.py +773 -550
  20. spotifywebapipython/spotifyconnect/spotifyconnectdirectorytask.py +37 -16
  21. spotifywebapipython/spotifyconnect/spotifyconnectzeroconfexceptions.py +10 -0
  22. {spotifywebapiPython-1.0.151.dist-info → spotifywebapiPython-1.0.152.dist-info}/LICENSE +0 -0
  23. {spotifywebapiPython-1.0.151.dist-info → spotifywebapiPython-1.0.152.dist-info}/WHEEL +0 -0
  24. {spotifywebapiPython-1.0.151.dist-info → spotifywebapiPython-1.0.152.dist-info}/top_level.txt +0 -0
@@ -747,19 +747,28 @@ class SpotifyClient:
747
747
 
748
748
  def _ResolveDeviceObject(
749
749
  self,
750
- device:str | SpotifyConnectDevice
750
+ device:str | SpotifyConnectDevice,
751
+ activateDevice:bool=True,
751
752
  ) -> SpotifyConnectDevice:
752
753
  """
753
754
  Resolves a SpotifyConnectDevice object from a device identifier, name, or
754
- SpotifyConnectDevice object argument value. An exception will be raised
755
- if the `device` argument could not be resolved or activated.
755
+ SpotifyConnectDevice object argument value. A null object will be returned
756
+ if the `device` argument could not be resolved.
756
757
 
757
758
  Args:
758
- device (str|SpotifyConnectDevice):
759
- A device identifier, name, or a SpotifyConnectDevice object.
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.
760
769
 
761
770
  Returns:
762
- A SpotifyConnectDevice object for the specified device.
771
+ A SpotifyConnectDevice object for the specified device if found; otherwise, null.
763
772
 
764
773
  Raises:
765
774
  SpotifyApiError:
@@ -780,13 +789,13 @@ class SpotifyClient:
780
789
 
781
790
  else:
782
791
 
783
- # no - ensure the specified device id / name is active and available,
784
- # and return the SpotifyConnectDevice object.
785
- # an exception will be thrown if the device id could not be activated.
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.
786
795
  scDevice = self.GetSpotifyConnectDevice(
787
796
  device,
788
797
  refreshDeviceList=True,
789
- activateDevice=True)
798
+ activateDevice=activateDevice)
790
799
 
791
800
  return scDevice
792
801
 
@@ -1153,12 +1162,16 @@ class SpotifyClient:
1153
1162
  Example: `spotify:track:6zd8T1PBe9JFHmuVnurdRp,spotify:track:1kWUud3vY5ij5r62zxpTRy`.
1154
1163
  An unlimited number of items can be added in one request, but the more items the longer it
1155
1164
  will take.
1156
- deviceId (str):
1157
- The id or name of the device this command is targeting.
1158
- If not supplied, the user's currently active device is the target.
1159
- Example: `0d1841b0976bae2a3a310dd74c0f3df354899bc8`
1160
- Example: `Web Player (Chrome)`
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.
1161
1173
  verifyDeviceId (bool):
1174
+ DEPRECATED - deviceId is automatically verified as of v1.0.149.
1162
1175
  True to verify a device id is active; otherwise, false to assume that a
1163
1176
  device id is already active.
1164
1177
  Default is True.
@@ -1180,7 +1193,7 @@ class SpotifyClient:
1180
1193
 
1181
1194
  The Spotify Web API endpoint is called to add each item individually.
1182
1195
  The Spotify Web API does not currently support adding more than 1 item to the queue at a time.
1183
-
1196
+
1184
1197
  <details>
1185
1198
  <summary>Sample Code</summary>
1186
1199
  ```python
@@ -1197,14 +1210,12 @@ class SpotifyClient:
1197
1210
  apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
1198
1211
  apiMethodParms.AppendKeyValue("uris", uris)
1199
1212
  apiMethodParms.AppendKeyValue("deviceId", deviceId)
1200
- apiMethodParms.AppendKeyValue("verifyDeviceId", verifyDeviceId)
1213
+ apiMethodParms.AppendKeyValue("verifyDeviceId (DEPRECATED)", verifyDeviceId)
1201
1214
  apiMethodParms.AppendKeyValue("delay", delay)
1202
1215
  _logsi.LogMethodParmList(SILevel.Verbose, "Add items to playback queue", apiMethodParms)
1203
1216
 
1204
1217
  # validations.
1205
1218
  delay = validateDelay(delay, 0.15, 10)
1206
- if (verifyDeviceId is None):
1207
- verifyDeviceId = True
1208
1219
 
1209
1220
  # build a list of all item uri's.
1210
1221
  # remove any leading / trailing spaces in case user put a space between the items.
@@ -1217,7 +1228,7 @@ class SpotifyClient:
1217
1228
  arrUris[idx] = arrUris[idx].strip()
1218
1229
 
1219
1230
  # resolve / activate the device object if needed.
1220
- scDevice:SpotifyConnectDevice = self._ResolveDeviceObject(deviceId)
1231
+ scDevice:SpotifyConnectDevice = self._ResolveDeviceObject(deviceId, False)
1221
1232
 
1222
1233
  # process all uri's.
1223
1234
  for idx in range(0, len(arrUris)):
@@ -1227,7 +1238,8 @@ class SpotifyClient:
1227
1238
  {
1228
1239
  'uri': arrUris[idx]
1229
1240
  }
1230
- urlParms['device_id'] = scDevice.Id
1241
+ if (scDevice is not None):
1242
+ urlParms['device_id'] = scDevice.Id
1231
1243
 
1232
1244
  # execute spotify web api request.
1233
1245
  msg:SpotifyApiMessage = SpotifyApiMessage(apiMethodName, '/me/player/queue')
@@ -1980,7 +1992,7 @@ class SpotifyClient:
1980
1992
  A comma-separated list of Spotify User ID's to check.
1981
1993
  Maximum: 5 ID's.
1982
1994
  Example: `1kWUud3vY5ij5r62zxpTRy,2takcwOaAZWiXQijPHIx7B`
1983
- Deprecated - A single item list containing current user's Spotify Username; Maximum of 1 id.
1995
+ DEPRECATED - A single item list containing current user's Spotify Username; Maximum of 1 id.
1984
1996
 
1985
1997
  Returns:
1986
1998
  Array of boolean, containing a single boolean status that indicates
@@ -2014,7 +2026,7 @@ class SpotifyClient:
2014
2026
  # trace.
2015
2027
  apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
2016
2028
  apiMethodParms.AppendKeyValue("playlistId", playlistId)
2017
- apiMethodParms.AppendKeyValue("userIds", userIds)
2029
+ apiMethodParms.AppendKeyValue("userIds (DEPRECATED)", userIds)
2018
2030
  _logsi.LogMethodParmList(SILevel.Verbose, "Check to see if users are following a playlist", apiMethodParms)
2019
2031
 
2020
2032
  # validations.
@@ -5471,7 +5483,8 @@ class SpotifyClient:
5471
5483
  Example: `sv_SE`
5472
5484
  refresh (bool):
5473
5485
  True to return real-time information from the spotify web api and
5474
- 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.
5475
5488
 
5476
5489
  Returns:
5477
5490
  A `list[Category]` object that contains the list category details.
@@ -6089,6 +6102,94 @@ class SpotifyClient:
6089
6102
  _logsi.LeaveMethod(SILevel.Debug, apiMethodName)
6090
6103
 
6091
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
+
6092
6193
  def GetEpisode(
6093
6194
  self,
6094
6195
  episodeId:str=None,
@@ -7491,7 +7592,8 @@ class SpotifyClient:
7491
7592
  def GetPlayerPlaybackState(
7492
7593
  self,
7493
7594
  market:str=None,
7494
- additionalTypes:str=None
7595
+ additionalTypes:str=None,
7596
+ refresh:bool=True,
7495
7597
  ) -> PlayerPlayState:
7496
7598
  """
7497
7599
  Get information about the user's current playback state, including track or episode, progress,
@@ -7515,6 +7617,10 @@ class SpotifyClient:
7515
7617
  Note: This parameter was introduced to allow existing clients to maintain their current behaviour
7516
7618
  and might be deprecated in the future. In addition to providing this parameter, make sure that your client
7517
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.
7518
7624
 
7519
7625
  Returns:
7520
7626
  A `PlayerPlayState` object that contains player state details as well as
@@ -7543,8 +7649,25 @@ class SpotifyClient:
7543
7649
  # trace.
7544
7650
  apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
7545
7651
  apiMethodParms.AppendKeyValue("market", market)
7652
+ apiMethodParms.AppendKeyValue("additionalTypes", additionalTypes)
7653
+ apiMethodParms.AppendKeyValue("refresh", refresh)
7546
7654
  _logsi.LogMethodParmList(SILevel.Verbose, "Get the user's current playback state", apiMethodParms)
7547
-
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
+
7548
7671
  # ensure market was either supplied or implied; default if neither.
7549
7672
  market = self._ValidateMarket(market)
7550
7673
 
@@ -7563,9 +7686,12 @@ class SpotifyClient:
7563
7686
 
7564
7687
  # process results.
7565
7688
  result = PlayerPlayState(root=msg.ResponseData)
7566
-
7689
+
7690
+ # update cache with result.
7691
+ self._ConfigurationCache[apiMethodName] = result
7692
+
7567
7693
  # is this an episode? if so, then determine the item type (e.g. podcast or audiobook).
7568
- if (result is not None):
7694
+ if (not result.IsEmpty):
7569
7695
  if (result.Item is not None):
7570
7696
  result.ItemType = SpotifyClient.GetTypeFromUri(result.Item.Uri)
7571
7697
  if (result.CurrentlyPlayingType == 'episode'):
@@ -7577,7 +7703,7 @@ class SpotifyClient:
7577
7703
 
7578
7704
  # check for a restricted device; if found, then grab the device id from the
7579
7705
  # cached list of spotify connect devices (if present).
7580
- if result is not None:
7706
+ if (not result.IsEmpty):
7581
7707
  device:Device = result.Device
7582
7708
  if (device is not None) and (device.Id is None or device.Id == ''):
7583
7709
  cacheKey:str = 'GetSpotifyConnectDevices'
@@ -7589,7 +7715,7 @@ class SpotifyClient:
7589
7715
 
7590
7716
  # update Spotify Connect Directory with active device details.
7591
7717
  # IMPORTANT - make sure result contains an object, otherwise it's an endless loop!
7592
- if result is not None:
7718
+ if (result is not None):
7593
7719
  self.SpotifyConnectDirectory.UpdateActiveDevice(result)
7594
7720
 
7595
7721
  # trace.
@@ -7610,173 +7736,389 @@ class SpotifyClient:
7610
7736
  _logsi.LeaveMethod(SILevel.Debug, apiMethodName)
7611
7737
 
7612
7738
 
7613
- def GetPlayerQueueInfo(self) -> PlayerQueueInfo:
7614
- """
7615
- Get the list of objects that make up the user's playback queue.
7616
-
7617
- This method requires the `user-read-currently-playing` and `user-read-playback-state` scope.
7618
-
7619
- Returns:
7620
- A `PlayerQueueInfo` object that contains the player queue information.
7621
-
7622
- Raises:
7623
- SpotifyWebApiError:
7624
- If the Spotify Web API request was for a non-authorization service
7625
- and the response contains error information.
7626
- SpotifyApiError:
7627
- If the method fails for any other reason.
7628
-
7629
- <details>
7630
- <summary>Sample Code</summary>
7631
- ```python
7632
- .. include:: ../docs/include/samplecode/SpotifyClient/GetPlayerQueueInfo.py
7633
- ```
7634
- </details>
7635
- """
7636
- apiMethodName:str = 'GetPlayerQueueInfo'
7637
- result:PlayHistoryPage = None
7638
-
7639
- try:
7640
-
7641
- # trace.
7642
- _logsi.EnterMethod(SILevel.Debug, apiMethodName)
7643
- _logsi.LogVerbose("Get the list of objects that make up the user's playback queue")
7644
-
7645
- # execute spotify web api request.
7646
- msg:SpotifyApiMessage = SpotifyApiMessage(apiMethodName, '/me/player/queue')
7647
- msg.RequestHeaders[self.AuthToken.HeaderKey] = self.AuthToken.HeaderValue
7648
- self.MakeRequest('GET', msg)
7649
-
7650
- # process results.
7651
- result = PlayerQueueInfo(root=msg.ResponseData)
7652
- result.DateLastRefreshed = datetime.utcnow().timestamp()
7653
-
7654
- # trace.
7655
- _logsi.LogObject(SILevel.Verbose, TRACE_METHOD_RESULT_TYPE % (apiMethodName, type(result).__name__), result, excludeNonPublic=True)
7656
- return result
7657
-
7658
- except SpotifyApiError: raise # pass handled exceptions on thru
7659
- except SpotifyWebApiError: raise # pass handled exceptions on thru
7660
- except SpotifyWebApiAuthenticationError: raise # pass handled exceptions on thru
7661
- except Exception as ex:
7662
-
7663
- # format unhandled exception.
7664
- raise SpotifyApiError(SAAppMessages.UNHANDLED_EXCEPTION.format(apiMethodName, str(ex)), ex, logsi=_logsi)
7665
-
7666
- finally:
7667
-
7668
- # trace.
7669
- _logsi.LeaveMethod(SILevel.Debug, apiMethodName)
7670
-
7671
-
7672
- def GetPlayerRecentTracks(
7739
+ def GetPlayerPlaybackStateSonos(
7673
7740
  self,
7674
- limit:int=50,
7675
- after:int=None,
7676
- before:int=None,
7677
- limitTotal:int=None
7678
- ) -> PlayHistoryPage:
7741
+ scDevice:SpotifyConnectDevice,
7742
+ ) -> PlayerPlayState:
7679
7743
  """
7680
- Get tracks from the current user's recently played tracks.
7681
- Note: Currently doesn't support podcast episodes.
7682
-
7683
- This method requires the `user-read-recently-played` scope.
7744
+ Get information about the Sonos user's current playback state, including track or episode, progress,
7745
+ and active device.
7684
7746
 
7747
+ This is not part of the Spotify Web API specification; it is for Sonos devices only!
7748
+
7685
7749
  Args:
7686
- limit (int):
7687
- The maximum number of items to return in a page of items when manual paging is used.
7688
- Default: 20, Range: 1 to 50.
7689
- See the `limitTotal` argument for automatic paging option.
7690
- after (int):
7691
- Returns all items after (but not including) this cursor position, which is
7692
- a Unix timestamp in milliseconds.
7693
- If `after` is specified, `before` must not be specified.
7694
- Use with limit to get the next set of items.
7695
- Default: `0` (the first item).
7696
- before (int):
7697
- Returns all items before (but not including) this cursor position, which is
7698
- a Unix timestamp in milliseconds.
7699
- If `before` is specified, `after` must not be specified.
7700
- Use with limit to get the next set of items.
7701
- Default: `0` (the first item).
7702
- limitTotal (int):
7703
- The maximum number of items to return for the request.
7704
- If specified, this argument overrides the limit and offset argument values
7705
- and paging is automatically used to retrieve all available items up to the
7706
- maximum number specified.
7707
- Default: None (disabled)
7708
-
7709
- Spotify currently limits the maximum number of items returned to 50 no matter what you
7710
- supply for the `limitTotal` argument.
7711
-
7712
- The `after` and `before` arguments are based upon local time (not UTC time). Recently
7713
- played item history uses a local timestamp, and NOT a UTC timestamp.
7714
-
7715
- If both `after` and `before` arguments are null (or zero), then a `before` value
7716
- is generated that will retrieve the last 50 recently played tracks.
7750
+ scDevice (SpotifyConnectDevice):
7751
+ Spotify Connect device instance used to identify the Sonos player.
7717
7752
 
7718
7753
  Returns:
7719
- A `PlayHistoryPage` object that contains the recently played items.
7754
+ A `PlayerPlayState` object that contains Sonos player state details as well as
7755
+ currently playing content.
7720
7756
 
7721
7757
  Raises:
7722
- SpotifyWebApiError:
7723
- If the Spotify Web API request was for a non-authorization service
7724
- and the response contains error information.
7725
7758
  SpotifyApiError:
7726
7759
  If the method fails for any other reason.
7727
-
7728
- <details>
7729
- <summary>Sample Code - After DateTime</summary>
7730
- ```python
7731
- .. include:: ../docs/include/samplecode/SpotifyClient/GetPlayerRecentTracks_AfterDateTime.py
7732
- ```
7733
- </details>
7734
- <details>
7735
- <summary>Sample Code - For Past 1 Hour</summary>
7736
- ```python
7737
- .. include:: ../docs/include/samplecode/SpotifyClient/GetPlayerRecentTracks_Past01Hours.py
7738
- ```
7739
- </details>
7740
7760
  """
7741
- apiMethodName:str = 'GetPlayerRecentTracks'
7761
+ apiMethodName:str = 'GetPlayerPlaybackStateSonos'
7742
7762
  apiMethodParms:SIMethodParmListContext = None
7743
- result:PlayHistoryPage = None
7744
7763
 
7745
7764
  try:
7746
7765
 
7747
7766
  # trace.
7748
7767
  apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
7749
- apiMethodParms.AppendKeyValue("limit", limit)
7750
- apiMethodParms.AppendKeyValue("after", after)
7751
- apiMethodParms.AppendKeyValue("before", before)
7752
- apiMethodParms.AppendKeyValue("limitTotal", limitTotal)
7753
- _logsi.LogMethodParmList(SILevel.Verbose, "Get tracks from the current user's recently played tracks", apiMethodParms)
7768
+ apiMethodParms.AppendKeyValue("scDevice", scDevice)
7769
+ _logsi.LogMethodParmList(SILevel.Verbose, "Get Sonos device current playback state", apiMethodParms)
7754
7770
 
7755
- # validations.
7756
- if before is None:
7757
- before = 0
7758
- if (not isinstance(before,int)):
7759
- before = int(before)
7771
+ # create playstate object.
7772
+ playerState = PlayerPlayState()
7760
7773
 
7761
- if after is None:
7762
- after = 0
7763
- if (not isinstance(after,int)):
7764
- after = int(after)
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)
7765
7777
 
7766
- if limit is None:
7767
- limit = 20
7768
- if not isinstance(limitTotal, int):
7769
- limitTotal = 0
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)
7770
7783
 
7771
- # if no before or after values, then get the last 50 tracks played.
7772
- if (after == 0) and (before == 0):
7773
- before = GetUnixTimestampMSFromUtcNow(seconds=-1)
7774
- limitTotal = 100 # spotify only returns 50 max.
7775
- _logsi.LogVerbose("Defaulting to retrieve play history of the last 50 recently played items (before = %d)" % before)
7776
-
7777
- # are we auto-paging? if so, then use max limit.
7778
- if limitTotal > 0:
7779
- limit = 50
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
+
7955
+ def GetPlayerQueueInfo(self) -> PlayerQueueInfo:
7956
+ """
7957
+ Get the list of objects that make up the user's playback queue.
7958
+
7959
+ This method requires the `user-read-currently-playing` and `user-read-playback-state` scope.
7960
+
7961
+ Returns:
7962
+ A `PlayerQueueInfo` object that contains the player queue information.
7963
+
7964
+ Raises:
7965
+ SpotifyWebApiError:
7966
+ If the Spotify Web API request was for a non-authorization service
7967
+ and the response contains error information.
7968
+ SpotifyApiError:
7969
+ If the method fails for any other reason.
7970
+
7971
+ <details>
7972
+ <summary>Sample Code</summary>
7973
+ ```python
7974
+ .. include:: ../docs/include/samplecode/SpotifyClient/GetPlayerQueueInfo.py
7975
+ ```
7976
+ </details>
7977
+ """
7978
+ apiMethodName:str = 'GetPlayerQueueInfo'
7979
+ result:PlayHistoryPage = None
7980
+
7981
+ try:
7982
+
7983
+ # trace.
7984
+ _logsi.EnterMethod(SILevel.Debug, apiMethodName)
7985
+ _logsi.LogVerbose("Get the list of objects that make up the user's playback queue")
7986
+
7987
+ # execute spotify web api request.
7988
+ msg:SpotifyApiMessage = SpotifyApiMessage(apiMethodName, '/me/player/queue')
7989
+ msg.RequestHeaders[self.AuthToken.HeaderKey] = self.AuthToken.HeaderValue
7990
+ self.MakeRequest('GET', msg)
7991
+
7992
+ # process results.
7993
+ result = PlayerQueueInfo(root=msg.ResponseData)
7994
+ result.DateLastRefreshed = datetime.utcnow().timestamp()
7995
+
7996
+ # trace.
7997
+ _logsi.LogObject(SILevel.Verbose, TRACE_METHOD_RESULT_TYPE % (apiMethodName, type(result).__name__), result, excludeNonPublic=True)
7998
+ return result
7999
+
8000
+ except SpotifyApiError: raise # pass handled exceptions on thru
8001
+ except SpotifyWebApiError: raise # pass handled exceptions on thru
8002
+ except SpotifyWebApiAuthenticationError: raise # pass handled exceptions on thru
8003
+ except Exception as ex:
8004
+
8005
+ # format unhandled exception.
8006
+ raise SpotifyApiError(SAAppMessages.UNHANDLED_EXCEPTION.format(apiMethodName, str(ex)), ex, logsi=_logsi)
8007
+
8008
+ finally:
8009
+
8010
+ # trace.
8011
+ _logsi.LeaveMethod(SILevel.Debug, apiMethodName)
8012
+
8013
+
8014
+ def GetPlayerRecentTracks(
8015
+ self,
8016
+ limit:int=50,
8017
+ after:int=None,
8018
+ before:int=None,
8019
+ limitTotal:int=None
8020
+ ) -> PlayHistoryPage:
8021
+ """
8022
+ Get tracks from the current user's recently played tracks.
8023
+ Note: Currently doesn't support podcast episodes.
8024
+
8025
+ This method requires the `user-read-recently-played` scope.
8026
+
8027
+ Args:
8028
+ limit (int):
8029
+ The maximum number of items to return in a page of items when manual paging is used.
8030
+ Default: 20, Range: 1 to 50.
8031
+ See the `limitTotal` argument for automatic paging option.
8032
+ after (int):
8033
+ Returns all items after (but not including) this cursor position, which is
8034
+ a Unix timestamp in milliseconds.
8035
+ If `after` is specified, `before` must not be specified.
8036
+ Use with limit to get the next set of items.
8037
+ Default: `0` (the first item).
8038
+ before (int):
8039
+ Returns all items before (but not including) this cursor position, which is
8040
+ a Unix timestamp in milliseconds.
8041
+ If `before` is specified, `after` must not be specified.
8042
+ Use with limit to get the next set of items.
8043
+ Default: `0` (the first item).
8044
+ limitTotal (int):
8045
+ The maximum number of items to return for the request.
8046
+ If specified, this argument overrides the limit and offset argument values
8047
+ and paging is automatically used to retrieve all available items up to the
8048
+ maximum number specified.
8049
+ Default: None (disabled)
8050
+
8051
+ Spotify currently limits the maximum number of items returned to 50 no matter what you
8052
+ supply for the `limitTotal` argument.
8053
+
8054
+ The `after` and `before` arguments are based upon local time (not UTC time). Recently
8055
+ played item history uses a local timestamp, and NOT a UTC timestamp.
8056
+
8057
+ If both `after` and `before` arguments are null (or zero), then a `before` value
8058
+ is generated that will retrieve the last 50 recently played tracks.
8059
+
8060
+ Returns:
8061
+ A `PlayHistoryPage` object that contains the recently played items.
8062
+
8063
+ Raises:
8064
+ SpotifyWebApiError:
8065
+ If the Spotify Web API request was for a non-authorization service
8066
+ and the response contains error information.
8067
+ SpotifyApiError:
8068
+ If the method fails for any other reason.
8069
+
8070
+ <details>
8071
+ <summary>Sample Code - After DateTime</summary>
8072
+ ```python
8073
+ .. include:: ../docs/include/samplecode/SpotifyClient/GetPlayerRecentTracks_AfterDateTime.py
8074
+ ```
8075
+ </details>
8076
+ <details>
8077
+ <summary>Sample Code - For Past 1 Hour</summary>
8078
+ ```python
8079
+ .. include:: ../docs/include/samplecode/SpotifyClient/GetPlayerRecentTracks_Past01Hours.py
8080
+ ```
8081
+ </details>
8082
+ """
8083
+ apiMethodName:str = 'GetPlayerRecentTracks'
8084
+ apiMethodParms:SIMethodParmListContext = None
8085
+ result:PlayHistoryPage = None
8086
+
8087
+ try:
8088
+
8089
+ # trace.
8090
+ apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
8091
+ apiMethodParms.AppendKeyValue("limit", limit)
8092
+ apiMethodParms.AppendKeyValue("after", after)
8093
+ apiMethodParms.AppendKeyValue("before", before)
8094
+ apiMethodParms.AppendKeyValue("limitTotal", limitTotal)
8095
+ _logsi.LogMethodParmList(SILevel.Verbose, "Get tracks from the current user's recently played tracks", apiMethodParms)
8096
+
8097
+ # validations.
8098
+ if before is None:
8099
+ before = 0
8100
+ if (not isinstance(before,int)):
8101
+ before = int(before)
8102
+
8103
+ if after is None:
8104
+ after = 0
8105
+ if (not isinstance(after,int)):
8106
+ after = int(after)
8107
+
8108
+ if limit is None:
8109
+ limit = 20
8110
+ if not isinstance(limitTotal, int):
8111
+ limitTotal = 0
8112
+
8113
+ # if no before or after values, then get the last 50 tracks played.
8114
+ if (after == 0) and (before == 0):
8115
+ before = GetUnixTimestampMSFromUtcNow(seconds=-1)
8116
+ limitTotal = 100 # spotify only returns 50 max.
8117
+ _logsi.LogVerbose("Defaulting to retrieve play history of the last 50 recently played items (before = %d)" % before)
8118
+
8119
+ # are we auto-paging? if so, then use max limit.
8120
+ if limitTotal > 0:
8121
+ limit = 50
7780
8122
  if limit > limitTotal:
7781
8123
  limit = limitTotal
7782
8124
  result = PlayHistoryPage()
@@ -9122,263 +9464,50 @@ class SpotifyClient:
9122
9464
  ```python
9123
9465
  .. include:: ../docs/include/samplecode/SpotifyClient/GetShows.py
9124
9466
  ```
9125
- </details>
9126
- """
9127
- apiMethodName:str = 'GetShows'
9128
- apiMethodParms:SIMethodParmListContext = None
9129
- result:list[ShowSimplified] = []
9130
-
9131
- try:
9132
-
9133
- # trace.
9134
- apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
9135
- apiMethodParms.AppendKeyValue("ids", ids)
9136
- apiMethodParms.AppendKeyValue("market", market)
9137
- _logsi.LogMethodParmList(SILevel.Verbose, "Get Spotify catalog information for multiple shows", apiMethodParms)
9138
-
9139
- # ensure market was either supplied or implied; default if neither.
9140
- market = self._ValidateMarket(market)
9141
-
9142
- # build spotify web api request parameters.
9143
- urlParms:dict = \
9144
- {
9145
- 'ids': ids,
9146
- }
9147
- if market is not None:
9148
- urlParms['market'] = market
9149
-
9150
- # execute spotify web api request.
9151
- msg:SpotifyApiMessage = SpotifyApiMessage(apiMethodName, '/shows')
9152
- msg.RequestHeaders[self.AuthToken.HeaderKey] = self.AuthToken.HeaderValue
9153
- msg.UrlParameters = urlParms
9154
- self.MakeRequest('GET', msg)
9155
-
9156
- # process results.
9157
- items = msg.ResponseData.get('shows',None)
9158
- if items is not None:
9159
- for item in items:
9160
- result.append(ShowSimplified(root=item))
9161
-
9162
- # trace.
9163
- _logsi.LogArray(SILevel.Verbose, TRACE_METHOD_RESULT_TYPE % (apiMethodName, 'list[ShowSimplified]'), result)
9164
- return result
9165
-
9166
- except SpotifyApiError: raise # pass handled exceptions on thru
9167
- except SpotifyWebApiError: raise # pass handled exceptions on thru
9168
- except SpotifyWebApiAuthenticationError: raise # pass handled exceptions on thru
9169
- except Exception as ex:
9170
-
9171
- # format unhandled exception.
9172
- raise SpotifyApiError(SAAppMessages.UNHANDLED_EXCEPTION.format(apiMethodName, str(ex)), ex, logsi=_logsi)
9173
-
9174
- finally:
9175
-
9176
- # trace.
9177
- _logsi.LeaveMethod(SILevel.Debug, apiMethodName)
9178
-
9179
-
9180
- def GetSonosPlayerPlaybackState(
9181
- self,
9182
- scDevice:SpotifyConnectDevice,
9183
- ) -> PlayerPlayState:
9184
- """
9185
- Get information about the Sonos user's current playback state, including track or episode, progress,
9186
- and active device.
9187
-
9188
- This is not part of the Spotify Web API specification; it is for Sonos devices only!
9189
-
9190
- Args:
9191
- scDevice (SpotifyConnectDevice):
9192
- Spotify Connect device instance used to identify the Sonos player.
9193
-
9194
- Returns:
9195
- A `PlayerPlayState` object that contains Sonos player state details as well as
9196
- currently playing content.
9197
-
9198
- Raises:
9199
- SpotifyApiError:
9200
- If the method fails for any other reason.
9467
+ </details>
9201
9468
  """
9202
- apiMethodName:str = 'GetSonosPlayerPlaybackState'
9469
+ apiMethodName:str = 'GetShows'
9203
9470
  apiMethodParms:SIMethodParmListContext = None
9471
+ result:list[ShowSimplified] = []
9204
9472
 
9205
9473
  try:
9206
9474
 
9207
9475
  # trace.
9208
9476
  apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
9209
- apiMethodParms.AppendKeyValue("scDevice", scDevice)
9210
- _logsi.LogMethodParmList(SILevel.Verbose, "Get Sonos user's current playback state", apiMethodParms)
9211
-
9212
- # create playstate object.
9213
- playerState = PlayerPlayState()
9214
-
9215
- # get Sonos Controller instance for the device.
9216
- sonosPlayer:Soco = self.SpotifyConnectDirectory.GetSonosPlayer(scDevice)
9217
-
9218
- # use the SoCo API to get Sonos playback status.
9219
- # build a spotify playback status instance with equivalent Sonos state values.
9220
- _logsi.LogVerbose("Getting Sonos device status via Sonos Control API for device: %s" % (scDevice.Title))
9221
- sonosTrackInfo:dict = sonosPlayer.get_current_track_info()
9222
- _logsi.LogDictionary(SILevel.Verbose, "Sonos device current_track_info results for device: %s" % (scDevice.Title), sonosTrackInfo, prettyPrint=True)
9223
- sonosTransportInfo:dict = sonosPlayer.get_current_transport_info()
9224
- _logsi.LogDictionary(SILevel.Verbose, "Sonos device get_current_transport_info results for device: %s" % (scDevice.Title), sonosTransportInfo, prettyPrint=True)
9225
-
9226
- # update Spotify Web API player state with Sonos player state.
9227
- playerState._Device._Id = scDevice.Id
9228
- playerState._Device._Name = sonosPlayer.player_name
9229
- playerState._Device._VolumePercent = sonosPlayer.volume
9230
- playerState._Device._IsActive = True
9231
- playerState._Device._IsRestricted = True
9232
- playerState._Device._SupportsVolume = True
9233
- playerState._Device._Type = 'SPEAKER'
9234
-
9235
- # try to get the spotify uri from the "metadata" music library DIDL value, as
9236
- # the "uri" value (e.g. "x-sonos-vli:RINCON_38420B909DC801400:2,spotify:e934941535d7b182815bf688490ce8a8")
9237
- # is not a true spotify uri value (e.g. "spotify:track:6kYyS9g4WJeRzTYqsmcMmM")
9238
- spotifyUri:str = sonosTrackInfo.get('uri','')
9239
- didl:str = sonosTrackInfo.get('metadata',None)
9240
- if (didl):
9241
- METADATA_ID:str = 'x-sonos-spotify:'
9242
- idx:int = didl.find(METADATA_ID)
9243
- if (idx > -1):
9244
- spotifyUri:str = didl[idx + len(METADATA_ID):]
9245
- spotifyUri = urllib.parse.unquote(spotifyUri)
9246
- idx = spotifyUri.find('?')
9247
- if (idx > -1):
9248
- spotifyUri = spotifyUri[:idx]
9249
- spotifyType = self.GetTypeFromUri(spotifyUri) or ''
9250
- spotifyId = self.GetIdFromUri(spotifyUri) or ''
9251
-
9252
- # set base item properties.
9253
- playerState.IsEmpty = False
9254
- playerState.ItemType = spotifyType
9255
- sTimeValue:str = sonosTrackInfo.get('position',None)
9256
- playerState._ProgressMS = mediaPositionHMS_toSeconds(sTimeValue) * 1000 # convert h:mm:ss to milliseconds
9477
+ apiMethodParms.AppendKeyValue("ids", ids)
9478
+ apiMethodParms.AppendKeyValue("market", market)
9479
+ _logsi.LogMethodParmList(SILevel.Verbose, "Get Spotify catalog information for multiple shows", apiMethodParms)
9257
9480
 
9258
- # set some item properties based on the playing type (episode or non-episode).
9259
- if (spotifyType == 'episode'):
9481
+ # ensure market was either supplied or implied; default if neither.
9482
+ market = self._ValidateMarket(market)
9260
9483
 
9261
- # get the episode data from spotify, as Sonos Soco data is incomplete.
9262
- playerState._CurrentlyPlayingType = 'episode'
9263
- episode:Episode = self.GetEpisode(spotifyId)
9264
- if (episode.Id is not None):
9265
- playerState._Item = episode
9266
- if (playerState._Context is None):
9267
- playerState._Context = Context()
9268
- playerState._Context._Type = episode.Show.Type
9269
- playerState._Context._Uri = episode.Show.Uri
9270
- playerState._Context._Href = episode.Show.Href
9271
- playerState._Context._ExternalUrls = episode.Show.ExternalUrls
9272
- else:
9273
- # if data could not be obtained from spotify, then use what's
9274
- # available from Sonos Soco api metadata.
9275
- playerState._Item = Episode()
9276
- playerState._Item._Name = sonosTrackInfo.get('title','')
9277
- playerState._Item._Uri = spotifyUri
9278
- playerState._Item._Description = 'Sonos device does not provide a description'
9279
- sTimeValue:str = sonosTrackInfo.get('duration',None)
9280
- playerState._Item._DurationMS = mediaPositionHMS_toSeconds(sTimeValue) * 1000 # convert h:mm:ss to milliseconds
9281
- playerState._Item._Explicit = False
9282
- playerState._Item._Href = 'https://api.spotify.com/v1/episodes/' + spotifyId
9283
- playerState._Item._HtmlDescription = playerState._Item._Description
9284
- playerState._Item._Id = spotifyId
9285
- playerState._Item.Images.append(ImageObject())
9286
- playerState._Item.Images[0]._Url = sonosTrackInfo.get('album_art','')
9287
- playerState._Item._TrackNumber = sonosTrackInfo.get('playlist_position','')
9288
- playerState._Item._ReleaseDate = '0000'
9289
- playerState._Item._ReleaseDatePrecision = 'year'
9290
- playerState._Item._Type = 'episode'
9291
- playerState._Item._Show = Show()
9292
- playerState._Item._Show._Name = sonosTrackInfo.get('album','') # TODO get this from metadata (<r:podcast>The Elfstones of Shannara</r:podcast>)
9293
- playerState._Item._Show.Images.append(ImageObject())
9294
- playerState._Item._Show.Images[0]._Url = sonosTrackInfo.get('album_art','')
9295
- playerState._Item._Show._Publisher = sonosTrackInfo.get('artist','')
9484
+ # build spotify web api request parameters.
9485
+ urlParms:dict = \
9486
+ {
9487
+ 'ids': ids,
9488
+ }
9489
+ if market is not None:
9490
+ urlParms['market'] = market
9296
9491
 
9297
- # if show name not set then get it from metadata.
9298
- if (playerState._Item._Show._Name == ''):
9299
- if (didl):
9300
- METADATA_ID:str = '<r:podcast>'
9301
- METADATA_END:str = '</r:podcast>'
9302
- idx:int = didl.find(METADATA_ID)
9303
- if (idx > -1):
9304
- metaValue:str = didl[idx + len(METADATA_ID):]
9305
- idx = metaValue.find(METADATA_END)
9306
- if (idx > -1):
9307
- metaValue = metaValue[:idx]
9308
- playerState._Item._Show._Name = metaValue
9309
-
9310
- # if episode is playing then resolve the underlying type (audiobook / podcast show).
9311
- if (self.IsChapterEpisode(spotifyId)):
9312
- playerState.ItemType = 'audiobook'
9313
- else:
9314
- playerState.ItemType = 'podcast'
9315
-
9316
- elif (spotifyType == 'track'):
9317
-
9318
- # get the track data from spotify, as Sonos Soco data is incomplete.
9319
- track:Track = self.GetTrack(spotifyId)
9320
- if (track.Id is not None):
9321
- playerState._Item = track
9322
- if (playerState._Context is None):
9323
- playerState._Context = Context()
9324
- playerState._Context._Type = track.Album.Type
9325
- playerState._Context._Uri = track.Album.Uri
9326
- playerState._Context._Href = track.Album.Href
9327
- playerState._Context._ExternalUrls = track.Album.ExternalUrls
9328
- else:
9329
- # if data could not be obtained from spotify, then use what's
9330
- # available from Sonos Soco api metadata.
9331
- playerState._CurrentlyPlayingType = 'track'
9332
- playerState.ItemType = playerState._CurrentlyPlayingType
9333
- playerState._Item = Track()
9334
- playerState._Item._Name = sonosTrackInfo.get('title','')
9335
- playerState._Item._Uri = spotifyUri
9336
- playerState._Item._Description = 'Sonos device does not provide a description'
9337
- sTimeValue:str = sonosTrackInfo.get('duration',None)
9338
- playerState._Item._DurationMS = mediaPositionHMS_toSeconds(sTimeValue) * 1000 # convert h:mm:ss to milliseconds
9339
- playerState._Item._Explicit = False
9340
- playerState._Item._Href = 'https://api.spotify.com/v1/episodes/' + spotifyId
9341
- playerState._Item._HtmlDescription = playerState._Item._Description
9342
- playerState._Item._Id = spotifyId
9343
- playerState._Item._TrackNumber = sonosTrackInfo.get('playlist_position','')
9344
- playerState._Item._ReleaseDate = '0000'
9345
- playerState._Item._ReleaseDatePrecision = 'year'
9346
- playerState._Item._Type = 'track'
9347
- playerState._Item._Album = Album()
9348
- playerState._Item._Album._Name = sonosTrackInfo.get('album','')
9349
- playerState._Item._Album.Images.append(ImageObject())
9350
- playerState._Item._Album.Images[0]._Url = sonosTrackInfo.get('album_art','')
9351
- playerState._Item.Artists.append(Artist())
9352
- playerState._Item.Artists[0]._Name = sonosTrackInfo.get('artist','')
9353
-
9354
- # set transport actions.
9355
- currentTransportState:str = sonosTransportInfo.get('current_transport_state','')
9356
- if currentTransportState == 'PLAYING':
9357
- playerState._IsPlaying = True
9358
- elif currentTransportState in ['PAUSED_PLAYBACK','STOPPED']:
9359
- playerState.Actions._Pausing = True
9360
-
9361
- # only update the following PlayState attributes from SoCo if they are NOT set.
9362
- # these values are used to set the Sonos PlayMode value, which is the actual value that determines
9363
- # what the Repeat and Shuffle settings are (ya, it's weird!). What is odd is that the Spotify
9364
- # Web API PlayerState values for Shuffle and Repeat can be different than what the Sonos Controller
9365
- # API (SoCo) report for the device! This is why we use the Spotify Web API PlayerState values.
9366
- if playerState._ShuffleState is None:
9367
- playerState._ShuffleState = sonosPlayer.shuffle
9368
- if playerState._RepeatState is None:
9369
- sonosRepeat = sonosPlayer.repeat
9370
- if sonosRepeat == 'ONE':
9371
- playerState._RepeatState = 'track'
9372
- elif sonosRepeat == False:
9373
- playerState._RepeatState = 'off'
9374
- elif sonosRepeat == True:
9375
- playerState._RepeatState = 'context'
9492
+ # execute spotify web api request.
9493
+ msg:SpotifyApiMessage = SpotifyApiMessage(apiMethodName, '/shows')
9494
+ msg.RequestHeaders[self.AuthToken.HeaderKey] = self.AuthToken.HeaderValue
9495
+ msg.UrlParameters = urlParms
9496
+ self.MakeRequest('GET', msg)
9497
+
9498
+ # process results.
9499
+ items = msg.ResponseData.get('shows',None)
9500
+ if items is not None:
9501
+ for item in items:
9502
+ result.append(ShowSimplified(root=item))
9376
9503
 
9377
9504
  # trace.
9378
- _logsi.LogObject(SILevel.Verbose, "Current Spotify player PlayState (built from Sonos device current state) for device: %s" % (scDevice.Title), playerState, excludeNonPublic=True)
9379
- return playerState
9505
+ _logsi.LogArray(SILevel.Verbose, TRACE_METHOD_RESULT_TYPE % (apiMethodName, 'list[ShowSimplified]'), result)
9506
+ return result
9380
9507
 
9381
9508
  except SpotifyApiError: raise # pass handled exceptions on thru
9509
+ except SpotifyWebApiError: raise # pass handled exceptions on thru
9510
+ except SpotifyWebApiAuthenticationError: raise # pass handled exceptions on thru
9382
9511
  except Exception as ex:
9383
9512
 
9384
9513
  # format unhandled exception.
@@ -9393,7 +9522,7 @@ class SpotifyClient:
9393
9522
  def GetSpotifyConnectDevice(
9394
9523
  self,
9395
9524
  deviceValue:str,
9396
- verifyUserContext:bool=True, # DEPRECATED
9525
+ verifyUserContext:bool=True,
9397
9526
  verifyTimeout:float=5.0,
9398
9527
  refreshDeviceList:bool=False,
9399
9528
  activateDevice:bool=True,
@@ -9439,7 +9568,7 @@ class SpotifyClient:
9439
9568
  Returns:
9440
9569
  A `SpotifyConnectDevice` object for the deviceValue if one could be resolved.
9441
9570
  otherwise, a null value with the understanding that subsequent operations will probably
9442
- fail since it's not in the Spotify Connect discovery list.
9571
+ fail since it's not in the Spotify Connect device list.
9443
9572
 
9444
9573
  Raises:
9445
9574
  SpotifyWebApiError:
@@ -9476,14 +9605,14 @@ class SpotifyClient:
9476
9605
  apiMethodName:str = 'GetSpotifyConnectDevice'
9477
9606
  apiMethodParms:SIMethodParmListContext = None
9478
9607
  scDevice:SpotifyConnectDevice = None
9479
- sonosPlayer:Soco = None
9608
+ sonosPlayer:SoCo = None
9480
9609
 
9481
9610
  try:
9482
9611
 
9483
9612
  # trace.
9484
9613
  apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
9485
9614
  apiMethodParms.AppendKeyValue("deviceValue", deviceValue)
9486
- apiMethodParms.AppendKeyValue("verifyUserContext", verifyUserContext) # DEPRECATED
9615
+ apiMethodParms.AppendKeyValue("verifyUserContext (DEPRECATED)", verifyUserContext)
9487
9616
  apiMethodParms.AppendKeyValue("verifyTimeout", verifyTimeout)
9488
9617
  apiMethodParms.AppendKeyValue("refreshDeviceList", refreshDeviceList)
9489
9618
  apiMethodParms.AppendKeyValue("activateDevice", activateDevice)
@@ -9499,8 +9628,15 @@ class SpotifyClient:
9499
9628
  refreshDeviceList = False
9500
9629
 
9501
9630
  # resolve the device id from the specified device value.
9502
- # an exception will be raised if the device could not be resolved!
9503
- 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
+
9504
9640
  discovery:ZeroconfDiscoveryResult = scDevice.DiscoveryResult
9505
9641
  info:ZeroconfGetInfo = scDevice.DeviceInfo
9506
9642
  scDevice.WasReConnected = False
@@ -9510,28 +9646,13 @@ class SpotifyClient:
9510
9646
  _logsi.LogVerbose("Spotify Connect device %s is already active; no need to re-activate" % (scDevice.Title))
9511
9647
  return scDevice
9512
9648
 
9513
- # is the device in the player device list? if so, then it's already to be selected. note that
9514
- # if another user is controlling the device, then it will not be in the active device list
9515
- # 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.
9516
9652
  if (scDevice.IsInDeviceList):
9517
9653
  _logsi.LogVerbose("Spotify Connect device %s is already in the available device list; no need to re-activate" % (scDevice.Title))
9518
9654
  return scDevice
9519
9655
 
9520
- # chromecast devices always require activation if they are not the active device.
9521
- # note that `transferPlayback=false`, as transfer should be done after we return to caller.
9522
- if (scDevice.IsChromeCast):
9523
-
9524
- # activate the spotify cast application on the device.
9525
- _logsi.LogVerbose("Activating Chromecast Spotify Connect device: %s" % (scDevice.Title))
9526
- self._SpotifyConnectDirectory.ActivateCastAppSpotify(scDevice.Name, transferPlayback=False)
9527
-
9528
- # re-fetch device instance, as it has updated Zeroconf Response data.
9529
- # do not need to refresh from Spotify Web API, as only zeroconf data was changed.
9530
- scDevice = self._SpotifyConnectDirectory.GetDevice(deviceValue, refreshDynamicDevices=False)
9531
-
9532
- # return device to caller.
9533
- return scDevice
9534
-
9535
9656
  # if this is a Sonos device and there is no Spotify Client Application token then don't bother.
9536
9657
  # in this case, we will exit and let the calling method switch to the Sonos device and play
9537
9658
  # using the Sonos local queue.
@@ -9555,13 +9676,28 @@ class SpotifyClient:
9555
9676
  _logsi.LogVerbose("Target device is Sonos; getting Sonos Controller instance for device %s" % (scDevice.Title))
9556
9677
  sonosPlayer = self.SpotifyConnectDirectory.GetSonosPlayer(scDevice)
9557
9678
 
9558
- # at this point, we will try to activate the device.
9559
-
9560
9679
  # was device activation requested? if not, then we are done.
9561
9680
  if (not activateDevice):
9562
9681
  _logsi.LogVerbose("Activation not requested for Spotify Connect device: %s" % (scDevice.Title))
9563
9682
  return scDevice
9564
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
+
9565
9701
  zcfResult:ZeroconfResponse
9566
9702
 
9567
9703
  # store the currently active user of the device, in case we need to switch users later on.
@@ -9734,7 +9870,7 @@ class SpotifyClient:
9734
9870
  # trace.
9735
9871
  apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
9736
9872
  apiMethodParms.AppendKeyValue("refresh", refresh)
9737
- apiMethodParms.AppendKeyValue("sortResult", sortResult) # DEPRECATED
9873
+ apiMethodParms.AppendKeyValue("sortResult (DEPRECATED)", sortResult)
9738
9874
  _logsi.LogMethodParmList(SILevel.Verbose, "Get available Spotify Connect devices", apiMethodParms)
9739
9875
 
9740
9876
  # validations.
@@ -11361,11 +11497,14 @@ class SpotifyClient:
11361
11497
  This method requires the `user-modify-playback-state` scope.
11362
11498
 
11363
11499
  Args:
11364
- deviceId (str):
11365
- The id or name of the device this command is targeting.
11366
- If not supplied, the user's currently active device is the target.
11367
- Example: `0d1841b0976bae2a3a310dd74c0f3df354899bc8`
11368
- Example: `Web Player (Chrome)`
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.
11369
11508
  delay (float):
11370
11509
  Time delay (in seconds) to wait AFTER issuing the command to the player.
11371
11510
  This delay will give the spotify web api time to process the change before
@@ -11405,20 +11544,21 @@ class SpotifyClient:
11405
11544
  delay = validateDelay(delay, 0.50, 10)
11406
11545
 
11407
11546
  # resolve / activate the device object if needed.
11408
- scDevice:SpotifyConnectDevice = self._ResolveDeviceObject(deviceId)
11547
+ scDevice:SpotifyConnectDevice = self._ResolveDeviceObject(deviceId, False)
11409
11548
 
11410
11549
  # is this an active Sonos device?
11411
- if (scDevice.IsSonos) and (scDevice.IsActiveDevice):
11550
+ if (scDevice is not None) and (scDevice.IsSonos):
11412
11551
 
11413
11552
  _logsi.LogVerbose("Issuing command to Sonos device %s: PAUSE" % (scDevice.Title))
11414
- sonosPlayer:Soco = self.SpotifyConnectDirectory.GetSonosPlayer(scDevice)
11553
+ sonosPlayer:SoCo = self.SpotifyConnectDirectory.GetSonosPlayer(scDevice)
11415
11554
  sonosPlayer.pause()
11416
11555
 
11417
11556
  else:
11418
11557
 
11419
11558
  # build spotify web api request parameters.
11420
11559
  urlParms:dict = {}
11421
- urlParms['device_id'] = scDevice.Id
11560
+ if (scDevice is not None):
11561
+ urlParms['device_id'] = scDevice.Id
11422
11562
 
11423
11563
  # execute spotify web api request.
11424
11564
  msg:SpotifyApiMessage = SpotifyApiMessage(apiMethodName, '/me/player/pause')
@@ -11487,11 +11627,14 @@ class SpotifyClient:
11487
11627
  player to start playing the next track.
11488
11628
  Default is `0`.
11489
11629
  Example: `25000`
11490
- deviceId (str):
11491
- The id or name of the device this command is targeting.
11492
- If not supplied, the user's currently active device is the target.
11493
- Example: `0d1841b0976bae2a3a310dd74c0f3df354899bc8`
11494
- Example: `Web Player (Chrome)`
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.
11495
11638
  delay (float):
11496
11639
  Time delay (in seconds) to wait AFTER issuing the command to the player.
11497
11640
  This delay will give the spotify web api time to process the change before
@@ -11556,7 +11699,7 @@ class SpotifyClient:
11556
11699
  delay = validateDelay(delay, 0.50, 10)
11557
11700
 
11558
11701
  # resolve / activate the device object if needed.
11559
- scDevice = self._ResolveDeviceObject(deviceId)
11702
+ scDevice = self._ResolveDeviceObject(deviceId, False)
11560
11703
 
11561
11704
  # build spotify web api request parameters.
11562
11705
  reqData:dict = \
@@ -11570,7 +11713,9 @@ class SpotifyClient:
11570
11713
  reqData['offset'] = { 'position': offsetPosition }
11571
11714
 
11572
11715
  # formulate url parms; will manually add them since we are adding json body as well.
11573
- urlParms:str = '?device_id=%s' % scDevice.Id
11716
+ urlParms:str = ''
11717
+ if (scDevice is not None):
11718
+ urlParms = '?device_id=%s' % scDevice.Id
11574
11719
 
11575
11720
  # execute spotify web api request.
11576
11721
  msg:SpotifyApiMessage = SpotifyApiMessage(apiMethodName, '/me/player/play{url_parms}'.format(url_parms=urlParms))
@@ -11614,11 +11759,14 @@ class SpotifyClient:
11614
11759
  and starts playing them.
11615
11760
 
11616
11761
  Args:
11617
- deviceId (str):
11618
- The id or name of the device this command is targeting.
11619
- If not supplied, the user's currently active device is the target.
11620
- Example: `0d1841b0976bae2a3a310dd74c0f3df354899bc8`
11621
- Example: `Web Player (Chrome)`
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.
11622
11770
  shuffle (bool):
11623
11771
  True to set player shuffle mode to on; otherwise, False for no shuffle.
11624
11772
  delay (float):
@@ -11696,11 +11844,11 @@ class SpotifyClient:
11696
11844
  arrUris.append(trackSaved.Track.Uri)
11697
11845
 
11698
11846
  # resolve / activate the device object if needed.
11699
- scDevice = self._ResolveDeviceObject(deviceId)
11847
+ scDevice = self._ResolveDeviceObject(deviceId, False)
11700
11848
 
11701
11849
  # set desired shuffle mode.
11702
11850
  if (shuffle == True):
11703
- self.PlayerSetShuffleMode(shuffle, scDevice.Id, delay)
11851
+ self.PlayerSetShuffleMode(shuffle, scDevice, delay)
11704
11852
 
11705
11853
  # play the tracks.
11706
11854
  # indicate device id has already been resolved.
@@ -11750,11 +11898,14 @@ class SpotifyClient:
11750
11898
  player to start playing the next track.
11751
11899
  Default is `0`.
11752
11900
  Example: `25000`
11753
- deviceId (str):
11754
- The id or name of the device this command is targeting.
11755
- If not supplied, the user's currently active device is the target.
11756
- Example: `0d1841b0976bae2a3a310dd74c0f3df354899bc8`
11757
- Example: `Web Player (Chrome)`
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.
11758
11909
  delay (float):
11759
11910
  Time delay (in seconds) to wait AFTER issuing the command to the player.
11760
11911
  This delay will give the spotify web api time to process the change before
@@ -11813,7 +11964,7 @@ class SpotifyClient:
11813
11964
  arrUris[idx] = arrUris[idx].strip()
11814
11965
 
11815
11966
  # resolve / activate the device object if needed.
11816
- scDevice = self._ResolveDeviceObject(deviceId)
11967
+ scDevice = self._ResolveDeviceObject(deviceId, False)
11817
11968
 
11818
11969
  # left this here in case we want to implement it in the future.
11819
11970
 
@@ -11843,7 +11994,9 @@ class SpotifyClient:
11843
11994
  reqData['uris'] = arrUris
11844
11995
 
11845
11996
  # formulate url parms; will manually add them since we are adding json body as well.
11846
- urlParms:str = '?device_id=%s' % scDevice.Id
11997
+ urlParms:str = ''
11998
+ if (scDevice is not None):
11999
+ urlParms = '?device_id=%s' % scDevice.Id
11847
12000
 
11848
12001
  # execute spotify web api request.
11849
12002
  msg:SpotifyApiMessage = SpotifyApiMessage(apiMethodName, '/me/player/play{url_parms}'.format(url_parms=urlParms))
@@ -11885,11 +12038,14 @@ class SpotifyClient:
11885
12038
  This method requires the `user-modify-playback-state` scope.
11886
12039
 
11887
12040
  Args:
11888
- deviceId (str):
11889
- The id or name of the device this command is targeting.
11890
- If not supplied, the user's currently active device is the target.
11891
- Example: `0d1841b0976bae2a3a310dd74c0f3df354899bc8`
11892
- Example: `Web Player (Chrome)`
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.
11893
12049
  delay (float):
11894
12050
  Time delay (in seconds) to wait AFTER issuing the command to the player.
11895
12051
  This delay will give the spotify web api time to process the change before
@@ -11906,7 +12062,7 @@ class SpotifyClient:
11906
12062
  This API only works for users who have Spotify Premium.
11907
12063
 
11908
12064
  The order of execution is not guaranteed when you use this API with other Player API endpoints.
11909
-
12065
+
11910
12066
  <details>
11911
12067
  <summary>Sample Code</summary>
11912
12068
  ```python
@@ -11929,20 +12085,21 @@ class SpotifyClient:
11929
12085
  delay = validateDelay(delay, 0.50, 10)
11930
12086
 
11931
12087
  # resolve / activate the device object if needed.
11932
- scDevice:SpotifyConnectDevice = self._ResolveDeviceObject(deviceId)
12088
+ scDevice:SpotifyConnectDevice = self._ResolveDeviceObject(deviceId, False)
11933
12089
 
11934
12090
  # is this an active Sonos device?
11935
- if (scDevice.IsSonos) and (scDevice.IsActiveDevice):
12091
+ if (scDevice is not None) and (scDevice.IsSonos):
11936
12092
 
11937
12093
  _logsi.LogVerbose("Issuing command to Sonos device %s: PLAY (RESUME)" % (scDevice.Title))
11938
- sonosPlayer:Soco = self.SpotifyConnectDirectory.GetSonosPlayer(scDevice)
12094
+ sonosPlayer:SoCo = self.SpotifyConnectDirectory.GetSonosPlayer(scDevice)
11939
12095
  sonosPlayer.play()
11940
12096
 
11941
12097
  else:
11942
12098
 
11943
12099
  # build spotify web api request parameters.
11944
12100
  urlParms:dict = {}
11945
- urlParms['device_id'] = scDevice.Id
12101
+ if (scDevice is not None):
12102
+ urlParms['device_id'] = scDevice.Id
11946
12103
 
11947
12104
  # execute spotify web api request.
11948
12105
  msg:SpotifyApiMessage = SpotifyApiMessage(apiMethodName, '/me/player/play')
@@ -11993,11 +12150,14 @@ class SpotifyClient:
11993
12150
  Passing in a position that is greater than the length of the track will cause the
11994
12151
  player to start playing the next song.
11995
12152
  Example = `25000` to start playing at the 25 second mark.
11996
- deviceId (str):
11997
- The id or name of the device this command is targeting.
11998
- If not supplied, the user's currently active device is the target.
11999
- Example: `0d1841b0976bae2a3a310dd74c0f3df354899bc8`
12000
- Example: `Web Player (Chrome)`
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.
12001
12161
  delay (float):
12002
12162
  Time delay (in seconds) to wait AFTER issuing the command to the player.
12003
12163
  This delay will give the spotify web api time to process the change before
@@ -12063,13 +12223,13 @@ class SpotifyClient:
12063
12223
  relativePositionMS = 0
12064
12224
 
12065
12225
  # resolve / activate the device object if needed.
12066
- scDevice:SpotifyConnectDevice = self._ResolveDeviceObject(deviceId)
12226
+ scDevice:SpotifyConnectDevice = self._ResolveDeviceObject(deviceId, False)
12067
12227
 
12068
12228
  # was relative seeking specified?
12069
- 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)):
12070
12230
 
12071
- # get current track position.
12072
- playState:PlayerPlayState = self.GetPlayerPlaybackState()
12231
+ # get current track position (from cache, since we called _ResolveDeviceObject method above).
12232
+ playState:PlayerPlayState = self.GetDevicePlaybackState(scDevice)
12073
12233
  newPositionMS:int = playState.ProgressMS
12074
12234
  if (newPositionMS is not None) and (newPositionMS > 0):
12075
12235
 
@@ -12080,11 +12240,11 @@ class SpotifyClient:
12080
12240
  positionMS = newPositionMS
12081
12241
 
12082
12242
  # is this an active Sonos device?
12083
- if (scDevice.IsSonos) and (scDevice.IsActiveDevice):
12243
+ if (scDevice is not None) and (scDevice.IsSonos):
12084
12244
 
12085
12245
  sonosPosition:str = mediaPositionHMS_fromSeconds(positionMS / 1000) # convert from milliseconds to Sonos H:MM:SS format
12086
12246
  _logsi.LogVerbose("Issuing command to Sonos device %s: SEEK (sonosPosition=%s)" % (scDevice.Title, sonosPosition))
12087
- sonosPlayer:Soco = self.SpotifyConnectDirectory.GetSonosPlayer(scDevice)
12247
+ sonosPlayer:SoCo = self.SpotifyConnectDirectory.GetSonosPlayer(scDevice)
12088
12248
  sonosPlayer.seek(str(sonosPosition))
12089
12249
 
12090
12250
  else:
@@ -12094,7 +12254,8 @@ class SpotifyClient:
12094
12254
  {
12095
12255
  'position_ms': positionMS
12096
12256
  }
12097
- urlParms['device_id'] = scDevice.Id
12257
+ if (scDevice is not None):
12258
+ urlParms['device_id'] = scDevice.Id
12098
12259
 
12099
12260
  # execute spotify web api request.
12100
12261
  msg:SpotifyApiMessage = SpotifyApiMessage(apiMethodName, '/me/player/seek')
@@ -12136,11 +12297,14 @@ class SpotifyClient:
12136
12297
  This method requires the `user-modify-playback-state` scope.
12137
12298
 
12138
12299
  Args:
12139
- deviceId (str):
12140
- The id or name of the device this command is targeting.
12141
- If not supplied, the user's currently active device is the target.
12142
- Example: `0d1841b0976bae2a3a310dd74c0f3df354899bc8`
12143
- Example: `Web Player (Chrome)`
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.
12144
12308
  delay (float):
12145
12309
  Time delay (in seconds) to wait AFTER issuing the command to the player.
12146
12310
  This delay will give the spotify web api time to process the change before
@@ -12180,20 +12344,21 @@ class SpotifyClient:
12180
12344
  delay = validateDelay(delay, 0.50, 10)
12181
12345
 
12182
12346
  # resolve / activate the device object if needed.
12183
- scDevice:SpotifyConnectDevice = self._ResolveDeviceObject(deviceId)
12347
+ scDevice:SpotifyConnectDevice = self._ResolveDeviceObject(deviceId, False)
12184
12348
 
12185
12349
  # is this an active Sonos device?
12186
- if (scDevice.IsSonos) and (scDevice.IsActiveDevice):
12350
+ if (scDevice is not None) and (scDevice.IsSonos):
12187
12351
 
12188
12352
  _logsi.LogVerbose("Issuing command to Sonos device %s: NEXT" % (scDevice.Title))
12189
- sonosPlayer:Soco = self.SpotifyConnectDirectory.GetSonosPlayer(scDevice)
12353
+ sonosPlayer:SoCo = self.SpotifyConnectDirectory.GetSonosPlayer(scDevice)
12190
12354
  sonosPlayer.next()
12191
12355
 
12192
12356
  else:
12193
12357
 
12194
12358
  # build spotify web api request parameters.
12195
12359
  urlParms:dict = {}
12196
- urlParms['device_id'] = scDevice.Id
12360
+ if (scDevice is not None):
12361
+ urlParms['device_id'] = scDevice.Id
12197
12362
 
12198
12363
  # execute spotify web api request.
12199
12364
  msg:SpotifyApiMessage = SpotifyApiMessage(apiMethodName, '/me/player/next')
@@ -12235,11 +12400,14 @@ class SpotifyClient:
12235
12400
  This method requires the `user-modify-playback-state` scope.
12236
12401
 
12237
12402
  Args:
12238
- deviceId (str):
12239
- The id or name of the device this command is targeting.
12240
- If not supplied, the user's currently active device is the target.
12241
- Example: `0d1841b0976bae2a3a310dd74c0f3df354899bc8`
12242
- Example: `Web Player (Chrome)`
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.
12243
12411
  delay (float):
12244
12412
  Time delay (in seconds) to wait AFTER issuing the command to the player.
12245
12413
  This delay will give the spotify web api time to process the change before
@@ -12279,20 +12447,21 @@ class SpotifyClient:
12279
12447
  delay = validateDelay(delay, 0.50, 10)
12280
12448
 
12281
12449
  # resolve / activate the device object if needed.
12282
- scDevice:SpotifyConnectDevice = self._ResolveDeviceObject(deviceId)
12450
+ scDevice:SpotifyConnectDevice = self._ResolveDeviceObject(deviceId, False)
12283
12451
 
12284
12452
  # is this an active Sonos device?
12285
- if (scDevice.IsSonos) and (scDevice.IsActiveDevice):
12453
+ if (scDevice is not None) and (scDevice.IsSonos):
12286
12454
 
12287
12455
  _logsi.LogVerbose("Issuing command to Sonos device %s: PREVIOUS" % (scDevice.Title))
12288
- sonosPlayer:Soco = self.SpotifyConnectDirectory.GetSonosPlayer(scDevice)
12456
+ sonosPlayer:SoCo = self.SpotifyConnectDirectory.GetSonosPlayer(scDevice)
12289
12457
  sonosPlayer.previous()
12290
12458
 
12291
12459
  else:
12292
12460
 
12293
12461
  # build spotify web api request parameters.
12294
12462
  urlParms:dict = {}
12295
- urlParms['device_id'] = scDevice.Id
12463
+ if (scDevice is not None):
12464
+ urlParms['device_id'] = scDevice.Id
12296
12465
 
12297
12466
  # execute spotify web api request.
12298
12467
  msg:SpotifyApiMessage = SpotifyApiMessage(apiMethodName, '/me/player/previous')
@@ -12341,11 +12510,14 @@ class SpotifyClient:
12341
12510
  - `context` - will repeat the current context.
12342
12511
  - `off` - will turn repeat off.
12343
12512
  Default: `off`
12344
- deviceId (str):
12345
- The id or name of the device this command is targeting.
12346
- If not supplied, the user's currently active device is the target.
12347
- Example: `0d1841b0976bae2a3a310dd74c0f3df354899bc8`
12348
- Example: `Web Player (Chrome)`
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.
12349
12521
  delay (float):
12350
12522
  Time delay (in seconds) to wait AFTER issuing the command to the player.
12351
12523
  This delay will give the spotify web api time to process the change before
@@ -12386,13 +12558,13 @@ class SpotifyClient:
12386
12558
  delay = validateDelay(delay, 0.50, 10)
12387
12559
 
12388
12560
  # resolve / activate the device object if needed.
12389
- scDevice:SpotifyConnectDevice = self._ResolveDeviceObject(deviceId)
12561
+ scDevice:SpotifyConnectDevice = self._ResolveDeviceObject(deviceId, False)
12390
12562
 
12391
12563
  # is this an active Sonos device?
12392
- if (scDevice.IsSonos) and (scDevice.IsActiveDevice):
12564
+ if (scDevice is not None) and (scDevice.IsSonos):
12393
12565
 
12394
12566
  # get current Spotify Connect player state.
12395
- playerState = self.GetSonosPlayerPlaybackState(scDevice)
12567
+ playerState = self.GetPlayerPlaybackStateSonos(scDevice)
12396
12568
 
12397
12569
  # set the Sonos PlayMode directly (instead of setting the Sonos repeat property)!
12398
12570
  # The Spotify Web API PlayerState `ShuffleState` value can sometimes be different than what the
@@ -12409,7 +12581,7 @@ class SpotifyClient:
12409
12581
 
12410
12582
  # execute SoCo api request.
12411
12583
  _logsi.LogVerbose("Issuing command to Sonos device %s: REPEAT (playmode=%s)" % (scDevice.Title, playMode))
12412
- sonosPlayer:Soco = self.SpotifyConnectDirectory.GetSonosPlayer(scDevice)
12584
+ sonosPlayer:SoCo = self.SpotifyConnectDirectory.GetSonosPlayer(scDevice)
12413
12585
  sonosPlayer.play_mode = playMode
12414
12586
 
12415
12587
  else:
@@ -12419,7 +12591,8 @@ class SpotifyClient:
12419
12591
  {
12420
12592
  'state': state
12421
12593
  }
12422
- urlParms['device_id'] = scDevice.Id
12594
+ if (scDevice is not None):
12595
+ urlParms['device_id'] = scDevice.Id
12423
12596
 
12424
12597
  # execute spotify web api request.
12425
12598
  msg:SpotifyApiMessage = SpotifyApiMessage(apiMethodName, '/me/player/repeat')
@@ -12467,11 +12640,14 @@ class SpotifyClient:
12467
12640
  - `True` - Shuffle user's playback.
12468
12641
  - `False` - Do not shuffle user's playback.
12469
12642
  Default is False
12470
- deviceId (str):
12471
- The id or name of the device this command is targeting.
12472
- If not supplied, the user's currently active device is the target.
12473
- Example: `0d1841b0976bae2a3a310dd74c0f3df354899bc8`
12474
- Example: `Web Player (Chrome)`
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.
12475
12651
  delay (float):
12476
12652
  Time delay (in seconds) to wait AFTER issuing the command to the player.
12477
12653
  This delay will give the spotify web api time to process the change before
@@ -12512,13 +12688,13 @@ class SpotifyClient:
12512
12688
  delay = validateDelay(delay, 0.50, 10)
12513
12689
 
12514
12690
  # resolve / activate the device object if needed.
12515
- scDevice:SpotifyConnectDevice = self._ResolveDeviceObject(deviceId)
12691
+ scDevice:SpotifyConnectDevice = self._ResolveDeviceObject(deviceId, False)
12516
12692
 
12517
12693
  # is this an active Sonos device?
12518
- if (scDevice.IsSonos) and (scDevice.IsActiveDevice):
12694
+ if (scDevice is not None) and (scDevice.IsSonos):
12519
12695
 
12520
12696
  # get current Spotify Connect player state.
12521
- playerState = self.GetSonosPlayerPlaybackState(scDevice)
12697
+ playerState = self.GetPlayerPlaybackStateSonos(scDevice)
12522
12698
 
12523
12699
  # set the Sonos PlayMode directly (instead of setting the Sonos `shuffle` property)!
12524
12700
  # the Spotify Web API PlayerState `RepeatState` value can sometimes be different than what the
@@ -12536,7 +12712,7 @@ class SpotifyClient:
12536
12712
 
12537
12713
  # execute SoCo api request.
12538
12714
  _logsi.LogVerbose("Issuing command to Sonos device %s: SHUFFLE (playmode=%s)" % (scDevice.Title, playMode))
12539
- sonosPlayer:Soco = self.SpotifyConnectDirectory.GetSonosPlayer(scDevice)
12715
+ sonosPlayer:SoCo = self.SpotifyConnectDirectory.GetSonosPlayer(scDevice)
12540
12716
  sonosPlayer.play_mode = playMode
12541
12717
 
12542
12718
  else:
@@ -12546,7 +12722,8 @@ class SpotifyClient:
12546
12722
  {
12547
12723
  'state': state
12548
12724
  }
12549
- urlParms['device_id'] = scDevice.Id
12725
+ if (scDevice is not None):
12726
+ urlParms['device_id'] = scDevice.Id
12550
12727
 
12551
12728
  # execute spotify web api request.
12552
12729
  msg:SpotifyApiMessage = SpotifyApiMessage(apiMethodName, '/me/player/shuffle')
@@ -12593,11 +12770,14 @@ class SpotifyClient:
12593
12770
  The volume to set.
12594
12771
  Must be a value from 0 to 100 inclusive.
12595
12772
  Example: `50`
12596
- deviceId (str):
12597
- The id or name of the device this command is targeting.
12598
- If not supplied, the user's currently active device is the target.
12599
- Example: `0d1841b0976bae2a3a310dd74c0f3df354899bc8`
12600
- Example: `Web Player (Chrome)`
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.
12601
12781
  delay (float):
12602
12782
  Time delay (in seconds) to wait AFTER issuing the command to the player.
12603
12783
  This delay will give the spotify web api time to process the change before
@@ -12638,13 +12818,13 @@ class SpotifyClient:
12638
12818
  delay = validateDelay(delay, 0.50, 10)
12639
12819
 
12640
12820
  # resolve / activate the device object if needed.
12641
- scDevice:SpotifyConnectDevice = self._ResolveDeviceObject(deviceId)
12821
+ scDevice:SpotifyConnectDevice = self._ResolveDeviceObject(deviceId, False)
12642
12822
 
12643
12823
  # is this an active Sonos device?
12644
- if (scDevice.IsSonos) and (scDevice.IsActiveDevice):
12824
+ if (scDevice is not None) and (scDevice.IsSonos):
12645
12825
 
12646
12826
  _logsi.LogVerbose("Issuing command to Sonos device %s: VOLUME = %s" % (scDevice.Title, volumePercent))
12647
- sonosPlayer:Soco = self.SpotifyConnectDirectory.GetSonosPlayer(scDevice)
12827
+ sonosPlayer:SoCo = self.SpotifyConnectDirectory.GetSonosPlayer(scDevice)
12648
12828
  sonosPlayer.volume = volumePercent
12649
12829
 
12650
12830
  else:
@@ -12654,7 +12834,8 @@ class SpotifyClient:
12654
12834
  {
12655
12835
  'volume_percent': volumePercent
12656
12836
  }
12657
- urlParms['device_id'] = scDevice.Id
12837
+ if (scDevice is not None):
12838
+ urlParms['device_id'] = scDevice.Id
12658
12839
 
12659
12840
  # execute spotify web api request.
12660
12841
  msg:SpotifyApiMessage = SpotifyApiMessage(apiMethodName, '/me/player/volume')
@@ -12692,6 +12873,7 @@ class SpotifyClient:
12692
12873
  delay:float=0.50,
12693
12874
  refreshDeviceList:bool=True,
12694
12875
  forceActivateDevice:bool=True,
12876
+ deviceIdFrom:str=None,
12695
12877
  ) -> None:
12696
12878
  """
12697
12879
  Transfer playback to a new Spotify Connect device and optionally begin playback.
@@ -12699,10 +12881,14 @@ class SpotifyClient:
12699
12881
  This method requires the `user-modify-playback-state` scope.
12700
12882
 
12701
12883
  Args:
12702
- deviceId (str):
12703
- The id or name of the device on which playback should be started/transferred.
12704
- Example: `0d1841b0976bae2a3a310dd74c0f3df354899bc8`
12705
- Example: `Web Player (Chrome)`
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.
12706
12892
  play (bool):
12707
12893
  The transfer method:
12708
12894
  - `True` - ensure playback happens on new device.
@@ -12722,6 +12908,13 @@ class SpotifyClient:
12722
12908
  force the device to reconnect to Spotify Connect; otherwise, False to not
12723
12909
  disconnect.
12724
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.
12725
12918
 
12726
12919
  Raises:
12727
12920
  SpotifyWebApiError:
@@ -12733,6 +12926,11 @@ class SpotifyClient:
12733
12926
  This API only works for users who have Spotify Premium.
12734
12927
 
12735
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!
12736
12934
 
12737
12935
  <details>
12738
12936
  <summary>Sample Code</summary>
@@ -12754,6 +12952,7 @@ class SpotifyClient:
12754
12952
  apiMethodParms.AppendKeyValue("delay", delay)
12755
12953
  apiMethodParms.AppendKeyValue("refreshDeviceList", refreshDeviceList)
12756
12954
  apiMethodParms.AppendKeyValue("forceActivateDevice", forceActivateDevice)
12955
+ apiMethodParms.AppendKeyValue("deviceIdFrom", deviceIdFrom)
12757
12956
  _logsi.LogMethodParmList(SILevel.Verbose, "Transfer playback to a new Spotify Connect device", apiMethodParms)
12758
12957
 
12759
12958
  # validations.
@@ -12765,8 +12964,32 @@ class SpotifyClient:
12765
12964
  if (forceActivateDevice is None):
12766
12965
  forceActivateDevice = True
12767
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
+
12768
12985
  # resolve / activate the device object if needed.
12769
- scDevice = self._ResolveDeviceObject(deviceId)
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)
12770
12993
 
12771
12994
  # is this a Sonos device?
12772
12995
  # if so, then control was automatically transferred by activating the device.