spotifywebapiPython 1.0.174__py3-none-any.whl → 1.0.176__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 (23) hide show
  1. {spotifywebapiPython-1.0.174.dist-info → spotifywebapiPython-1.0.176.dist-info}/METADATA +1 -1
  2. {spotifywebapiPython-1.0.174.dist-info → spotifywebapiPython-1.0.176.dist-info}/RECORD +23 -23
  3. spotifywebapipython/const.py +1 -1
  4. spotifywebapipython/docs/__init__.html +15 -3
  5. spotifywebapipython/docs/index.html +3 -3
  6. spotifywebapipython/docs/search.js +1 -1
  7. spotifywebapipython/docs/spotifywebapipython/const.html +4 -4
  8. spotifywebapipython/docs/spotifywebapipython/saappmessages.html +37 -3
  9. spotifywebapipython/docs/spotifywebapipython/sautils.html +3 -3
  10. spotifywebapipython/docs/spotifywebapipython/spotifyapierror.html +3 -3
  11. spotifywebapipython/docs/spotifywebapipython/spotifyapimessage.html +3 -3
  12. spotifywebapipython/docs/spotifywebapipython/spotifyauthtoken.html +3 -3
  13. spotifywebapipython/docs/spotifywebapipython/spotifyclient.html +45 -23
  14. spotifywebapipython/docs/spotifywebapipython/spotifydiscovery.html +3 -3
  15. spotifywebapipython/docs/spotifywebapipython/spotifywebapiauthenticationerror.html +3 -3
  16. spotifywebapipython/docs/spotifywebapipython/spotifywebapierror.html +3 -3
  17. spotifywebapipython/docs/spotifywebapipython/spotifywebplayertoken.html +3 -3
  18. spotifywebapipython/saappmessages.py +10 -0
  19. spotifywebapipython/spotifyclient.py +148 -80
  20. spotifywebapipython/spotifyconnect/spotifyconnectdirectorytask.py +50 -23
  21. {spotifywebapiPython-1.0.174.dist-info → spotifywebapiPython-1.0.176.dist-info}/LICENSE +0 -0
  22. {spotifywebapiPython-1.0.174.dist-info → spotifywebapiPython-1.0.176.dist-info}/WHEEL +0 -0
  23. {spotifywebapiPython-1.0.174.dist-info → spotifywebapiPython-1.0.176.dist-info}/top_level.txt +0 -0
@@ -10,6 +10,7 @@ from soco.core import (
10
10
  )
11
11
  from soco.plugins.sharelink import ShareLinkPlugin
12
12
  import time
13
+ import threading
13
14
  from typing import Tuple, Callable, Union
14
15
  from urllib3 import PoolManager, Timeout, HTTPResponse
15
16
  from urllib.parse import urlencode
@@ -208,6 +209,8 @@ class SpotifyClient:
208
209
  self._SpotifyConnectDiscoveryTimeout:float = float(spotifyConnectDiscoveryTimeout)
209
210
  self._SpotifyConnectDirectory:SpotifyConnectDirectoryTask = None
210
211
  self._SpotifyConnectDirectoryEnabled:bool = spotifyConnectDirectoryEnabled
212
+ self._SpotifyWebPlayerToken:SpotifyWebPlayerToken = None
213
+ self._SpotifyWebPlayerToken_RLock:threading.RLock = threading.RLock()
211
214
  self._TokenStorageDir:str = tokenStorageDir
212
215
  self._TokenStorageFile:str = tokenStorageFile
213
216
  self._TokenUpdater:Callable = tokenUpdater
@@ -366,8 +369,8 @@ class SpotifyClient:
366
369
  @property
367
370
  def HasSpotifyWebPlayerCredentials(self) -> bool:
368
371
  """
369
- Returns true if Spotify Web Player credentials have been configured in the Token Cache File;
370
- otherwise, False.
372
+ Returns true if Spotify Web Player credentials have been configured in the Token Cache File
373
+ for the current UserProfile loginid; otherwise, False.
371
374
  """
372
375
  return self._HasSpotifyWebPlayerCredentials
373
376
 
@@ -790,51 +793,58 @@ class SpotifyClient:
790
793
  otherwise, None.
791
794
 
792
795
  The value returned will be a `Bearer: token` string that can be used in a request header
793
- authorization key for Spotify Web API endpoints that start playback on a devoce.
796
+ authorization key for Spotify Web API endpoints that start playback on a device.
794
797
 
795
798
  The Token Cache File is queried for a key value of "SpotifyWebPlayerCookieCredentials/Shared/YOUR_SPOTIFY_LOGIN_ID",
796
799
  which will contain the `sp_key` and `sp_dc` values of the cookie credentials. These
797
800
  credentials are then converted into an authorization access token which is used by
798
- Spotify Web API endpoints to start playback on the specified device id.
801
+ Spotify Web API endpoints.
799
802
  """
800
- result:SpotifyWebPlayerToken = None
801
-
802
803
  # are spotify web player credentials configured? if not, then don't bother!
803
804
  if (not self._HasSpotifyWebPlayerCredentials):
804
- return result
805
+ return None
805
806
 
806
- # was a device argument passed? if not, then just create the token.
807
- if (scDevice is None):
807
+ # make the following thread-safe, so we don't retrieve multiple tokens.
808
+ with self._SpotifyWebPlayerToken_RLock:
808
809
 
809
- # get spotify web player access token info from spotify web player cookie credentials.
810
- _logsi.LogVerbose("Converting Spotify Web Player cookie credentials to an access token for loginId \"%s\"" % (self._SpotifyConnectLoginId))
811
- tokenWP = SpotifyWebPlayerToken(profileId=self._SpotifyConnectLoginId,
812
- tokenStorageDir=self._TokenStorageDir,
813
- tokenStorageFile=self._TokenStorageFile)
814
- if tokenWP is not None:
815
- result = tokenWP.HeaderValue
816
- _logsi.LogVerbose("Spotify Web Player authorization access token will be used")
810
+ # if token already retrieved and has not expired, then use it.
811
+ if (self._SpotifyWebPlayerToken is not None) and (not self._SpotifyWebPlayerToken.IsExpired):
812
+ _logsi.LogVerbose(SAAppMessages.MSG_SPOTIFY_WEB_PLAYER_TOKEN_INUSE, colorValue=SIColors.Gold)
813
+ return self._SpotifyWebPlayerToken.HeaderValue
817
814
 
818
- else:
815
+ # was a device argument passed? if not, then just refresh the token.
816
+ if (scDevice is None):
819
817
 
820
- # was the device resolved? if not, then don't bother!
821
- if (not isinstance(scDevice, SpotifyConnectDevice)):
822
- return result
818
+ # get spotify web player access token info from spotify web player cookie credentials.
819
+ _logsi.LogVerbose(SAAppMessages.MSG_SPOTIFY_WEB_PLAYER_TOKEN_REFRESHED % (self._SpotifyConnectLoginId), colorValue=SIColors.Gold)
820
+ self._SpotifyWebPlayerToken = SpotifyWebPlayerToken(
821
+ profileId=self._SpotifyConnectLoginId,
822
+ tokenStorageDir=self._TokenStorageDir,
823
+ tokenStorageFile=self._TokenStorageFile)
823
824
 
824
- # is the device restricted or Sonos?
825
- if (scDevice.IsSonos or scDevice.IsRestricted):
825
+ if self._SpotifyWebPlayerToken is not None:
826
+ _logsi.LogVerbose(SAAppMessages.MSG_SPOTIFY_WEB_PLAYER_TOKEN_INUSE, colorValue=SIColors.Gold)
827
+ return self._SpotifyWebPlayerToken.HeaderValue
828
+
829
+ else:
830
+
831
+ # was the device resolved? if not, then don't bother!
832
+ if (not isinstance(scDevice, SpotifyConnectDevice)):
833
+ return None
826
834
 
827
835
  # get spotify web player access token info from spotify web player cookie credentials.
828
- _logsi.LogVerbose("Converting Spotify Web Player cookie credentials to an access token for loginId \"%s\"" % (self._SpotifyConnectLoginId))
829
- tokenWP = SpotifyWebPlayerToken(profileId=self._SpotifyConnectLoginId,
830
- tokenStorageDir=self._TokenStorageDir,
831
- tokenStorageFile=self._TokenStorageFile)
832
- if tokenWP is not None:
833
- result = tokenWP.HeaderValue
834
- _logsi.LogVerbose("Playback will be started using Spotify Web Player authorization access token for device: %s" % (scDevice.Title))
836
+ _logsi.LogVerbose(SAAppMessages.MSG_SPOTIFY_WEB_PLAYER_TOKEN_REFRESHED % (self._SpotifyConnectLoginId), colorValue=SIColors.Gold)
837
+ self._SpotifyWebPlayerToken = SpotifyWebPlayerToken(
838
+ profileId=self._SpotifyConnectLoginId,
839
+ tokenStorageDir=self._TokenStorageDir,
840
+ tokenStorageFile=self._TokenStorageFile)
841
+
842
+ if self._SpotifyWebPlayerToken is not None:
843
+ _logsi.LogVerbose(SAAppMessages.MSG_SPOTIFY_WEB_PLAYER_TOKEN_INUSE, colorValue=SIColors.Gold)
844
+ return self._SpotifyWebPlayerToken.HeaderValue
835
845
 
836
846
  # return result to caller.
837
- return result
847
+ return None
838
848
 
839
849
 
840
850
 
@@ -1388,9 +1398,13 @@ class SpotifyClient:
1388
1398
  # resolve the device object from the device id.
1389
1399
  scDevice:SpotifyConnectDevice = self._ResolveDeviceObject(deviceId, False)
1390
1400
 
1401
+ # are spotify web player credentials configured? if so, then we will use them to create
1402
+ # an elevated authorization access token for the Spotify Web API endpoint call.
1403
+ accessTokenHeaderValue:str = self._GetSpotifyWebPlayerTokenHeaderValue(scDevice)
1404
+
1391
1405
  # is this an active Sonos device?
1392
1406
  # Sonos device can still be active, even if there is no active device in Spotify playstate.
1393
- if (scDevice is not None) and (scDevice.IsSonos):
1407
+ if (scDevice is not None) and (scDevice.IsSonos) and (accessTokenHeaderValue is None):
1394
1408
 
1395
1409
  # trace.
1396
1410
  _logsi.LogVerbose("Items will be added to Sonos local queue for device: %s" % (scDevice.Title))
@@ -1429,7 +1443,7 @@ class SpotifyClient:
1429
1443
 
1430
1444
  # execute spotify web api request.
1431
1445
  msg:SpotifyApiMessage = SpotifyApiMessage(apiMethodName, '/me/player/queue')
1432
- msg.RequestHeaders[self.AuthToken.HeaderKey] = self.AuthToken.HeaderValue
1446
+ msg.RequestHeaders[self.AuthToken.HeaderKey] = accessTokenHeaderValue or self.AuthToken.HeaderValue
1433
1447
  msg.UrlParameters = urlParms
1434
1448
  self.MakeRequest('POST', msg)
1435
1449
 
@@ -8463,7 +8477,16 @@ class SpotifyClient:
8463
8477
  If the Spotify Web API request was for a non-authorization service
8464
8478
  and the response contains error information.
8465
8479
  SpotifyApiError:
8466
- If the method fails for any other reason.
8480
+ If the method fails for any other reason.
8481
+
8482
+ <span class="warning">
8483
+ <b>DEPRECATED functionality as of 2024/11/27</b>
8484
+ For unauthorized Spotify Developer Applications, the api endpoint will return a `404 - Resource Not Found` exception if a Spotify Algorithmic playlist is specified.<br/>
8485
+ For authorized Spotify Developer Applications, the api endpoint will return details if a Spotify Algorithmic playlist is specified.<br/>
8486
+ More information about the deprecated functionality can be found on the
8487
+ <a href="https://developer.spotify.com/blog/2024-11-27-changes-to-the-web-api" target="_blank">Spotify Developer Forum Blog</a>
8488
+ page.
8489
+ <span>
8467
8490
 
8468
8491
  <details>
8469
8492
  <summary>Sample Code</summary>
@@ -8712,16 +8735,27 @@ class SpotifyClient:
8712
8735
  If the Spotify Web API request was for a non-authorization service
8713
8736
  and the response contains error information.
8714
8737
  SpotifyApiError:
8715
- If the method fails for any other reason.
8738
+ If the method fails for any other reason.
8739
+
8740
+ <span class="warning">
8741
+ <b>Notes regarding `limit_total` processing</b>
8742
+ There appears to be a bug in the Spotify Web API that forces you to use manual paging if
8743
+ utilizing the `fields` argument. It has been found that the API will only return a value
8744
+ of 50 (maximum) or less in the page `total` value if the `fields` argument is supplied.
8745
+ The API will return the total number of playlist items in the page `total` value if the `fields`
8746
+ argument is NOT supplied. A good playlist id to test this on is `1XhVM7jWPrGLTiNiAy97Za`,
8747
+ which is the largest playlist on spotify (4700+ items).
8748
+ <span>
8749
+
8750
+ <span class="warning">
8751
+ <b>DEPRECATED functionality as of 2024/11/27</b>
8752
+ Spotify Algorithmic playlists (e.g. "Made For You", "Daily Mix n", etc) will no longer be included
8753
+ in the returned results for unauthorized Spotify Developer Applications; they will be included in
8754
+ the results for authorized Spotify Developer Applications. More information about the deprecated
8755
+ functionality can be found on the <a href="https://developer.spotify.com/blog/2024-11-27-changes-to-the-web-api"
8756
+ target="_blank">Spotify Developer Forum Blog</a> page.
8757
+ <span>
8716
8758
 
8717
- * Notes regarding `limit_total` processing.
8718
- There appears to be a bug in the Spotify Web API that forces you to use manual paging if
8719
- utilizing the `fields` argument. It has been found that the API will only return a value
8720
- of 50 (maximum) or less in the page `total` value if the `fields` argument is supplied.
8721
- The API will return the total number of playlist items in the page `total` value if the `fields`
8722
- argument is NOT supplied. A good playlist id to test this on is `1XhVM7jWPrGLTiNiAy97Za`,
8723
- which is the largest playlist on spotify (4700+ items).
8724
-
8725
8759
  <details>
8726
8760
  <summary>Sample Code - Manual Paging</summary>
8727
8761
  ```python
@@ -8895,13 +8929,17 @@ class SpotifyClient:
8895
8929
  If the Spotify Web API request was for a non-authorization service
8896
8930
  and the response contains error information.
8897
8931
  SpotifyApiError:
8898
- If the method fails for any other reason.
8932
+ If the method fails for any other reason.
8933
+
8934
+ <span class="warning">
8935
+ <b>DEPRECATED functionality as of 2024/11/27</b>
8936
+ Spotify Algorithmic playlists (e.g. "Made For You", "Daily Mix n", etc) will no longer be included
8937
+ in the returned results for unauthorized Spotify Developer Applications; they will be included in
8938
+ the results for authorized Spotify Developer Applications. More information about the deprecated
8939
+ functionality can be found on the <a href="https://developer.spotify.com/blog/2024-11-27-changes-to-the-web-api"
8940
+ target="_blank">Spotify Developer Forum Blog</a> page.
8941
+ <span>
8899
8942
 
8900
- As of 2024/11/27, the Spotify Web API will only return user-defined playlists in the results. It will
8901
- not return any Spotify Algorithmic playlists (e.g. "Daily Mix 1", "Discover Weekly", and other "Made For You"
8902
- playlists). It will also not return playlist folders. More information can be found on the [Spotify Developer
8903
- Forum Blog post](https://developer.spotify.com/blog/2024-11-27-changes-to-the-web-api) that was conveyed on November 27, 2024.
8904
-
8905
8943
  <details>
8906
8944
  <summary>Sample Code - Manual Paging</summary>
8907
8945
  ```python
@@ -11757,7 +11795,7 @@ class SpotifyClient:
11757
11795
  This API only works for users who have Spotify Premium.
11758
11796
 
11759
11797
  The order of execution is not guaranteed when you use this API with other Player API endpoints.
11760
-
11798
+
11761
11799
  <details>
11762
11800
  <summary>Sample Code</summary>
11763
11801
  ```python
@@ -11782,9 +11820,13 @@ class SpotifyClient:
11782
11820
  # resolve the device object from the device id.
11783
11821
  scDevice:SpotifyConnectDevice = self._ResolveDeviceObject(deviceId, False)
11784
11822
 
11823
+ # are spotify web player credentials configured? if so, then we will use them to create
11824
+ # an elevated authorization access token for the Spotify Web API endpoint call.
11825
+ accessTokenHeaderValue:str = self._GetSpotifyWebPlayerTokenHeaderValue(scDevice)
11826
+
11785
11827
  # is this an active Sonos device?
11786
11828
  # Sonos device can still be active, even if there is no active device in Spotify playstate.
11787
- if (scDevice is not None) and (scDevice.IsSonos):
11829
+ if (scDevice is not None) and (scDevice.IsSonos) and (accessTokenHeaderValue is None):
11788
11830
 
11789
11831
  _logsi.LogVerbose("Issuing command to Sonos device %s: PAUSE" % (scDevice.Title))
11790
11832
  sonosPlayer:SoCo = self.SpotifyConnectDirectory.GetSonosPlayer(scDevice)
@@ -11803,7 +11845,7 @@ class SpotifyClient:
11803
11845
 
11804
11846
  # execute spotify web api request.
11805
11847
  msg:SpotifyApiMessage = SpotifyApiMessage(apiMethodName, '/me/player/pause')
11806
- msg.RequestHeaders[self.AuthToken.HeaderKey] = self.AuthToken.HeaderValue
11848
+ msg.RequestHeaders[self.AuthToken.HeaderKey] = accessTokenHeaderValue or self.AuthToken.HeaderValue
11807
11849
  msg.UrlParameters = urlParms
11808
11850
  self.MakeRequest('PUT', msg)
11809
11851
 
@@ -11944,10 +11986,8 @@ class SpotifyClient:
11944
11986
  # resolve the device object from the device idl activate if it's dormant.
11945
11987
  scDevice = self._ResolveDeviceObject(deviceId, True)
11946
11988
 
11947
- # are spotify web player credentials configured?
11948
- # if so, then we will use them to create an authorization access token for the
11949
- # Spotify Web API endpoint that starts playback on the device; this token has the
11950
- # required access to control "restricted" devices (such as Sonos).
11989
+ # are spotify web player credentials configured? if so, then we will use them to create
11990
+ # an elevated authorization access token for the Spotify Web API endpoint call.
11951
11991
  accessTokenHeaderValue:str = self._GetSpotifyWebPlayerTokenHeaderValue(scDevice)
11952
11992
 
11953
11993
  # is this an active Sonos device?
@@ -12303,10 +12343,8 @@ class SpotifyClient:
12303
12343
  # resolve the device object from the device id; activate if it's dormant.
12304
12344
  scDevice = self._ResolveDeviceObject(deviceId, True)
12305
12345
 
12306
- # are spotify web player credentials configured?
12307
- # if so, then we will use them to create an authorization access token for the
12308
- # Spotify Web API endpoint that starts playback on the device; this token has the
12309
- # required access to control "restricted" devices (such as Sonos).
12346
+ # are spotify web player credentials configured? if so, then we will use them to create
12347
+ # an elevated authorization access token for the Spotify Web API endpoint call.
12310
12348
  accessTokenHeaderValue:str = self._GetSpotifyWebPlayerTokenHeaderValue(scDevice)
12311
12349
 
12312
12350
  # is this an active Sonos device?
@@ -12474,9 +12512,13 @@ class SpotifyClient:
12474
12512
  # we activate the device here, as it may have been paused for a long time.
12475
12513
  scDevice:SpotifyConnectDevice = self._ResolveDeviceObject(deviceId, True)
12476
12514
 
12515
+ # are spotify web player credentials configured? if so, then we will use them to create
12516
+ # an elevated authorization access token for the Spotify Web API endpoint call.
12517
+ accessTokenHeaderValue:str = self._GetSpotifyWebPlayerTokenHeaderValue(scDevice)
12518
+
12477
12519
  # is this an active Sonos device?
12478
12520
  # Sonos device can still be active, even if there is no active device in Spotify playstate.
12479
- if (scDevice is not None) and (scDevice.IsSonos):
12521
+ if (scDevice is not None) and (scDevice.IsSonos) and (accessTokenHeaderValue is None):
12480
12522
 
12481
12523
  _logsi.LogVerbose("Issuing command to Sonos device %s: PLAY (RESUME)" % (scDevice.Title))
12482
12524
  sonosPlayer:SoCo = self.SpotifyConnectDirectory.GetSonosPlayer(scDevice)
@@ -12495,7 +12537,7 @@ class SpotifyClient:
12495
12537
 
12496
12538
  # execute spotify web api request.
12497
12539
  msg:SpotifyApiMessage = SpotifyApiMessage(apiMethodName, '/me/player/play')
12498
- msg.RequestHeaders[self.AuthToken.HeaderKey] = self.AuthToken.HeaderValue
12540
+ msg.RequestHeaders[self.AuthToken.HeaderKey] = accessTokenHeaderValue or self.AuthToken.HeaderValue
12499
12541
  msg.UrlParameters = urlParms
12500
12542
  self.MakeRequest('PUT', msg)
12501
12543
 
@@ -12631,9 +12673,13 @@ class SpotifyClient:
12631
12673
  newPositionMS = 0
12632
12674
  positionMS = newPositionMS
12633
12675
 
12676
+ # are spotify web player credentials configured? if so, then we will use them to create
12677
+ # an elevated authorization access token for the Spotify Web API endpoint call.
12678
+ accessTokenHeaderValue:str = self._GetSpotifyWebPlayerTokenHeaderValue(scDevice)
12679
+
12634
12680
  # is this an active Sonos device?
12635
12681
  # Sonos device can still be active, even if there is no active device in Spotify playstate.
12636
- if (scDevice is not None) and (scDevice.IsSonos):
12682
+ if (scDevice is not None) and (scDevice.IsSonos) and (accessTokenHeaderValue is None):
12637
12683
 
12638
12684
  sonosPosition:str = mediaPositionHMS_fromSeconds(positionMS / 1000) # convert from milliseconds to Sonos H:MM:SS format
12639
12685
  _logsi.LogVerbose("Issuing command to Sonos device %s: SEEK (sonosPosition=%s)" % (scDevice.Title, sonosPosition))
@@ -12656,7 +12702,7 @@ class SpotifyClient:
12656
12702
 
12657
12703
  # execute spotify web api request.
12658
12704
  msg:SpotifyApiMessage = SpotifyApiMessage(apiMethodName, '/me/player/seek')
12659
- msg.RequestHeaders[self.AuthToken.HeaderKey] = self.AuthToken.HeaderValue
12705
+ msg.RequestHeaders[self.AuthToken.HeaderKey] = accessTokenHeaderValue or self.AuthToken.HeaderValue
12660
12706
  msg.UrlParameters = urlParms
12661
12707
  self.MakeRequest('PUT', msg)
12662
12708
 
@@ -12743,9 +12789,13 @@ class SpotifyClient:
12743
12789
  # resolve the device object from the device id.
12744
12790
  scDevice:SpotifyConnectDevice = self._ResolveDeviceObject(deviceId, False)
12745
12791
 
12792
+ # are spotify web player credentials configured? if so, then we will use them to create
12793
+ # an elevated authorization access token for the Spotify Web API endpoint call.
12794
+ accessTokenHeaderValue:str = self._GetSpotifyWebPlayerTokenHeaderValue(scDevice)
12795
+
12746
12796
  # is this an active Sonos device?
12747
12797
  # Sonos device can still be active, even if there is no active device in Spotify playstate.
12748
- if (scDevice is not None) and (scDevice.IsSonos):
12798
+ if (scDevice is not None) and (scDevice.IsSonos) and (accessTokenHeaderValue is None):
12749
12799
 
12750
12800
  _logsi.LogVerbose("Issuing command to Sonos device %s: NEXT" % (scDevice.Title))
12751
12801
  sonosPlayer:SoCo = self.SpotifyConnectDirectory.GetSonosPlayer(scDevice)
@@ -12764,7 +12814,7 @@ class SpotifyClient:
12764
12814
 
12765
12815
  # execute spotify web api request.
12766
12816
  msg:SpotifyApiMessage = SpotifyApiMessage(apiMethodName, '/me/player/next')
12767
- msg.RequestHeaders[self.AuthToken.HeaderKey] = self.AuthToken.HeaderValue
12817
+ msg.RequestHeaders[self.AuthToken.HeaderKey] = accessTokenHeaderValue or self.AuthToken.HeaderValue
12768
12818
  msg.UrlParameters = urlParms
12769
12819
  self.MakeRequest('POST', msg)
12770
12820
 
@@ -12851,9 +12901,13 @@ class SpotifyClient:
12851
12901
  # resolve the device object from the device id.
12852
12902
  scDevice:SpotifyConnectDevice = self._ResolveDeviceObject(deviceId, False)
12853
12903
 
12904
+ # are spotify web player credentials configured? if so, then we will use them to create
12905
+ # an elevated authorization access token for the Spotify Web API endpoint call.
12906
+ accessTokenHeaderValue:str = self._GetSpotifyWebPlayerTokenHeaderValue(scDevice)
12907
+
12854
12908
  # is this an active Sonos device?
12855
12909
  # Sonos device can still be active, even if there is no active device in Spotify playstate.
12856
- if (scDevice is not None) and (scDevice.IsSonos):
12910
+ if (scDevice is not None) and (scDevice.IsSonos) and (accessTokenHeaderValue is None):
12857
12911
 
12858
12912
  _logsi.LogVerbose("Issuing command to Sonos device %s: PREVIOUS" % (scDevice.Title))
12859
12913
  sonosPlayer:SoCo = self.SpotifyConnectDirectory.GetSonosPlayer(scDevice)
@@ -12872,7 +12926,7 @@ class SpotifyClient:
12872
12926
 
12873
12927
  # execute spotify web api request.
12874
12928
  msg:SpotifyApiMessage = SpotifyApiMessage(apiMethodName, '/me/player/previous')
12875
- msg.RequestHeaders[self.AuthToken.HeaderKey] = self.AuthToken.HeaderValue
12929
+ msg.RequestHeaders[self.AuthToken.HeaderKey] = accessTokenHeaderValue or self.AuthToken.HeaderValue
12876
12930
  msg.UrlParameters = urlParms
12877
12931
  self.MakeRequest('POST', msg)
12878
12932
 
@@ -12967,9 +13021,13 @@ class SpotifyClient:
12967
13021
  # resolve the device object from the device id.
12968
13022
  scDevice:SpotifyConnectDevice = self._ResolveDeviceObject(deviceId, False)
12969
13023
 
13024
+ # are spotify web player credentials configured? if so, then we will use them to create
13025
+ # an elevated authorization access token for the Spotify Web API endpoint call.
13026
+ accessTokenHeaderValue:str = self._GetSpotifyWebPlayerTokenHeaderValue(scDevice)
13027
+
12970
13028
  # is this an active Sonos device?
12971
13029
  # Sonos device can still be active, even if there is no active device in Spotify playstate.
12972
- if (scDevice is not None) and (scDevice.IsSonos):
13030
+ if (scDevice is not None) and (scDevice.IsSonos) and (accessTokenHeaderValue is None):
12973
13031
 
12974
13032
  # get current Spotify Connect player state.
12975
13033
  playerState = self.GetPlayerPlaybackStateSonos(scDevice)
@@ -13008,7 +13066,7 @@ class SpotifyClient:
13008
13066
 
13009
13067
  # execute spotify web api request.
13010
13068
  msg:SpotifyApiMessage = SpotifyApiMessage(apiMethodName, '/me/player/repeat')
13011
- msg.RequestHeaders[self.AuthToken.HeaderKey] = self.AuthToken.HeaderValue
13069
+ msg.RequestHeaders[self.AuthToken.HeaderKey] = accessTokenHeaderValue or self.AuthToken.HeaderValue
13012
13070
  msg.UrlParameters = urlParms
13013
13071
  self.MakeRequest('PUT', msg)
13014
13072
 
@@ -13102,9 +13160,13 @@ class SpotifyClient:
13102
13160
  # resolve the device object from the device id.
13103
13161
  scDevice:SpotifyConnectDevice = self._ResolveDeviceObject(deviceId, False)
13104
13162
 
13163
+ # are spotify web player credentials configured? if so, then we will use them to create
13164
+ # an elevated authorization access token for the Spotify Web API endpoint call.
13165
+ accessTokenHeaderValue:str = self._GetSpotifyWebPlayerTokenHeaderValue(scDevice)
13166
+
13105
13167
  # is this an active Sonos device?
13106
13168
  # Sonos device can still be active, even if there is no active device in Spotify playstate.
13107
- if (scDevice is not None) and (scDevice.IsSonos):
13169
+ if (scDevice is not None) and (scDevice.IsSonos) and (accessTokenHeaderValue is None):
13108
13170
 
13109
13171
  # get current Spotify Connect player state.
13110
13172
  playerState = self.GetPlayerPlaybackStateSonos(scDevice)
@@ -13144,7 +13206,7 @@ class SpotifyClient:
13144
13206
 
13145
13207
  # execute spotify web api request.
13146
13208
  msg:SpotifyApiMessage = SpotifyApiMessage(apiMethodName, '/me/player/shuffle')
13147
- msg.RequestHeaders[self.AuthToken.HeaderKey] = self.AuthToken.HeaderValue
13209
+ msg.RequestHeaders[self.AuthToken.HeaderKey] = accessTokenHeaderValue or self.AuthToken.HeaderValue
13148
13210
  msg.UrlParameters = urlParms
13149
13211
  self.MakeRequest('PUT', msg)
13150
13212
 
@@ -13237,9 +13299,13 @@ class SpotifyClient:
13237
13299
  # resolve the device object from the device id.
13238
13300
  scDevice:SpotifyConnectDevice = self._ResolveDeviceObject(deviceId, False)
13239
13301
 
13302
+ # are spotify web player credentials configured? if so, then we will use them to create
13303
+ # an elevated authorization access token for the Spotify Web API endpoint call.
13304
+ accessTokenHeaderValue:str = self._GetSpotifyWebPlayerTokenHeaderValue(scDevice)
13305
+
13240
13306
  # is this an active Sonos device?
13241
13307
  # Sonos device can still be active, even if there is no active device in Spotify playstate.
13242
- if (scDevice is not None) and (scDevice.IsSonos):
13308
+ if (scDevice is not None) and (scDevice.IsSonos) and (accessTokenHeaderValue is None):
13243
13309
 
13244
13310
  _logsi.LogVerbose("Issuing command to Sonos device %s: VOLUME = %s" % (scDevice.Title, volumePercent))
13245
13311
  sonosPlayer:SoCo = self.SpotifyConnectDirectory.GetSonosPlayer(scDevice)
@@ -13261,7 +13327,7 @@ class SpotifyClient:
13261
13327
 
13262
13328
  # execute spotify web api request.
13263
13329
  msg:SpotifyApiMessage = SpotifyApiMessage(apiMethodName, '/me/player/volume')
13264
- msg.RequestHeaders[self.AuthToken.HeaderKey] = self.AuthToken.HeaderValue
13330
+ msg.RequestHeaders[self.AuthToken.HeaderKey] = accessTokenHeaderValue or self.AuthToken.HeaderValue
13265
13331
  msg.UrlParameters = urlParms
13266
13332
  self.MakeRequest('PUT', msg)
13267
13333
 
@@ -13411,12 +13477,16 @@ class SpotifyClient:
13411
13477
  # if not, then raise an exception as the Spotify Web API request will fail anyway.
13412
13478
  self._CheckForDeviceNotFound(scDevice, deviceId)
13413
13479
 
13480
+ # are spotify web player credentials configured? if so, then we will use them to create
13481
+ # an elevated authorization access token for the Spotify Web API endpoint call.
13482
+ accessTokenHeaderValue:str = self._GetSpotifyWebPlayerTokenHeaderValue()
13483
+
13414
13484
  # is this a Sonos device?
13415
13485
  # if so, then control was automatically transferred by activating the device.
13416
13486
  # note that the Spotify Web API cannot stop nor start play on the device either,
13417
13487
  # as it's considered a restricted device and the Spotify Web API cannot control it!
13418
13488
  # all requests to control the device will be made via the Sonos Controller API.
13419
- if (scDevice.IsSonos):
13489
+ if (scDevice.IsSonos) and (accessTokenHeaderValue is None):
13420
13490
 
13421
13491
  # get Sonos Controller instance for the device.
13422
13492
  sonosPlayer:SoCo = self.SpotifyConnectDirectory.GetSonosPlayer(scDevice)
@@ -13431,13 +13501,11 @@ class SpotifyClient:
13431
13501
  wasCmdIssued:bool = False
13432
13502
  if currentTransportState == 'PLAYING':
13433
13503
  if play == False:
13434
- _logsi.LogVerbose("Issuing command to Sonos device %s: PAUSE" % (scDevice.Title))
13435
- sonosPlayer.pause()
13504
+ self.PlayerMediaPause(scDevice)
13436
13505
  wasCmdIssued = True
13437
13506
  elif currentTransportState in ['PAUSED_PLAYBACK','STOPPED','TRANSITIONING']:
13438
13507
  if play == True:
13439
- _logsi.LogVerbose("Issuing command to Sonos device %s: PLAY" % (scDevice.Title))
13440
- sonosPlayer.play()
13508
+ self.PlayerMediaResume(scDevice)
13441
13509
  wasCmdIssued = True
13442
13510
 
13443
13511
  # give Sonos Controller time to process the change.
@@ -13480,7 +13548,7 @@ class SpotifyClient:
13480
13548
 
13481
13549
  # execute spotify web api request.
13482
13550
  msg:SpotifyApiMessage = SpotifyApiMessage(apiMethodName, '/me/player')
13483
- msg.RequestHeaders[self.AuthToken.HeaderKey] = self.AuthToken.HeaderValue
13551
+ msg.RequestHeaders[self.AuthToken.HeaderKey] = accessTokenHeaderValue or self.AuthToken.HeaderValue
13484
13552
  msg.RequestJson = reqData
13485
13553
  self.MakeRequest('PUT', msg)
13486
13554
 
@@ -1048,35 +1048,45 @@ class SpotifyConnectDirectoryTask(threading.Thread):
1048
1048
  if (sonosPlayer is None):
1049
1049
  raise SpotifyApiError("Could not find Sonos Controller instance for device: %s" % (device.Title), None, logsi=_logsi)
1050
1050
 
1051
+ # trace.
1052
+ _logsi.LogDictionary(SILevel.Verbose, "Sonos Controller instance for device: %s (speaker_info)" % (device.Title), sonosPlayer.speaker_info)
1053
+
1051
1054
  # does caller want the group coordinator?
1052
1055
  if (returnCoordinator):
1053
1056
 
1054
1057
  # TEST TODO - use to test Sonos coordinator functionality.
1055
1058
  # _logsi.LogObject(SILevel.Verbose, "Sonos Controller instance for device: %s" % (device.Title), sonosPlayer, colorValue=SIColors.Red)
1056
- # sonosPlayer._is_coordinator = False
1059
+ #sonosPlayer._is_coordinator = False
1057
1060
 
1058
- # if Sonos Controller instance is the group coordinator then we are done.
1061
+ # is Sonos Controller instance the group coordinator?
1059
1062
  if (sonosPlayer.is_coordinator):
1060
- _logsi.LogVerbose("Sonos Controller instance is a group coordinator for device: %s" % (device.Title))
1061
- _logsi.LogObject(SILevel.Verbose, "Sonos Controller instance for device: %s" % (device.Title), sonosPlayer)
1062
- _logsi.LogDictionary(SILevel.Verbose, "Sonos Controller instance for device: %s (speaker_info)" % (device.Title), sonosPlayer.speaker_info)
1063
- _logsi.LogObject(SILevel.Verbose, "Sonos Controller instance for device: %s (group)" % (device.Title), sonosPlayer.group)
1064
- _logsi.LogObject(SILevel.Verbose, "Sonos Controller instance for device: %s (zone_group_state)" % (device.Title), sonosPlayer.zone_group_state)
1065
- return sonosPlayer
1066
1063
 
1067
- # trace.
1068
- _logsi.LogObject(SILevel.Verbose, "Sonos Controller instance for device: %s" % (device.Title), sonosPlayer)
1069
- _logsi.LogDictionary(SILevel.Verbose, "Sonos Controller instance for device: %s (speaker_info)" % (device.Title), sonosPlayer.speaker_info)
1070
- _logsi.LogObject(SILevel.Verbose, "Sonos Controller instance for device: %s (group)" % (device.Title), sonosPlayer.group)
1071
- _logsi.LogObject(SILevel.Verbose, "Sonos Controller instance for device: %s (zone_group_state)" % (device.Title), sonosPlayer.zone_group_state)
1072
-
1073
- # is the device part of a group? if so, then use the group coordinator.
1074
- if (sonosPlayer.group is not None):
1075
- if (sonosPlayer.group.coordinator is not None):
1076
- sonosPlayerCoordinator:SoCo = sonosPlayer.group.coordinator
1077
- _logsi.LogVerbose("Sonos Controller group coordinator \"%s\" (%s) will be used for device: %s" % (sonosPlayerCoordinator.player_name, sonosPlayerCoordinator.ip_address, device.Title))
1078
- sonosPlayer = sonosPlayerCoordinator
1064
+ # yes - nothing else to do since it's already a group coordinator.
1065
+ _logsi.LogVerbose("Sonos Controller instance is a group coordinator")
1066
+
1067
+ else:
1079
1068
 
1069
+ # no - try to determine the group coordinator.
1070
+ _logsi.LogVerbose("Sonos Controller instance is NOT a group coordinator for device: %s" % (device.Title))
1071
+
1072
+ # is the device part of a group? if so, then use the `group.coordinator` value (if present).
1073
+ sonosPlayerCoordinator:SoCo = None
1074
+ if (sonosPlayer.group is not None):
1075
+ if (sonosPlayer.group.coordinator is not None):
1076
+ sonosPlayerCoordinator = sonosPlayer.group.coordinator
1077
+ sonosPlayer = sonosPlayerCoordinator
1078
+ _logsi.LogVerbose("Sonos Controller group coordinator \"%s\" (%s) will be used for device: %s" % (sonosPlayerCoordinator.player_name, sonosPlayerCoordinator.ip_address, device.Title))
1079
+ _logsi.LogDictionary(SILevel.Verbose, "Sonos Controller instance for device: %s (speaker_info)" % (device.Title), sonosPlayerCoordinator.speaker_info)
1080
+
1081
+ # if group coordinator could not be found, then log a trace message indicating
1082
+ # this and that subsequent Sonos Controller operations will probably fail.
1083
+ # this only seems to happen for "orphaned" Sonos devices (e.g. uid="")
1084
+ # zone_group_state.groups = [ZoneGroup(uid='RINCON_5CAAFDF4E8DE01400:orphan', coordinator=None, members={SoCo("192.168.50.79")}), etc
1085
+ if (sonosPlayerCoordinator is None):
1086
+ _logsi.LogVerbose("Sonos Controller group coordinator could not be determined for device: %s; subsequent Sonos Controller API functions will probably fail!" % (device.Title), colorValue=SIColors.Red)
1087
+ _logsi.LogObject(SILevel.Verbose, "Sonos Controller instance for device: %s (group)" % (device.Title), sonosPlayer.group)
1088
+
1089
+ # return Sonos Controller instance to caller.
1080
1090
  return sonosPlayer
1081
1091
 
1082
1092
 
@@ -1880,11 +1890,28 @@ class SpotifyConnectDirectoryTask(threading.Thread):
1880
1890
 
1881
1891
  try:
1882
1892
 
1883
- # if new device is a Sonos, then create a Sonos Controller instance for the device.
1884
- # use device ip address as the key, as Spotify Web API reports id=null for restricted devices in playerstate!
1893
+ # is this a Sonos device?
1885
1894
  if (scDevice.IsSonos):
1895
+
1896
+ # create a Sonos Controller instance for the device, retrieve the Sonos speaker information,
1897
+ # and add it to the Sonos players collection. use device ip address as the key, as the
1898
+ # Spotify Web API reports "id=null" for restricted devices in playerstate!
1886
1899
  _logsi.LogVerbose("Sonos device detected; creating Sonos Controller instance for device: %s (ip=%s)" % (scDevice.Title, zeroconfDiscoveryResult.HostIpAddress))
1887
- self._SonosPlayers[zeroconfDiscoveryResult.HostIpAddress] = SoCo(zeroconfDiscoveryResult.HostIpAddress)
1900
+ sonosPlayer:SoCo = SoCo(zeroconfDiscoveryResult.HostIpAddress)
1901
+ self._SonosPlayers[zeroconfDiscoveryResult.HostIpAddress] = sonosPlayer
1902
+
1903
+ try:
1904
+ sonosPlayer.get_speaker_info()
1905
+ except Exception as ex:
1906
+ _logsi.LogException("Could not get speaker info for Sonos Controller instance: %s" % (str(ex)), ex, logToSystemLogger=False)
1907
+
1908
+ # trace.
1909
+ if (_logsi.IsOn(SILevel.Verbose)):
1910
+ _logsi.LogObject(SILevel.Verbose, "Sonos Controller instance for device: %s" % (scDevice.Title), sonosPlayer)
1911
+ _logsi.LogDictionary(SILevel.Verbose, "Sonos Controller instance for device: %s (speaker_info)" % (scDevice.Title), sonosPlayer.speaker_info)
1912
+ _logsi.LogObject(SILevel.Verbose, "Sonos Controller instance for device: %s (group)" % (scDevice.Title), sonosPlayer.group)
1913
+ _logsi.LogObject(SILevel.Verbose, "Sonos Controller instance for device: %s (zone_group_state)" % (scDevice.Title), sonosPlayer.zone_group_state)
1914
+ _logsi.LogEnumerable(SILevel.Verbose, "Sonos Controller instance for device: %s (all_zones)" % (scDevice.Title), sonosPlayer.all_zones)
1888
1915
 
1889
1916
  except:
1890
1917